From 538c9d52b892f37754fbc44086a7b212d5791a27 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Thu, 6 Feb 2025 23:10:36 +0100 Subject: [PATCH 01/34] gui:controller support draft --- arcade/examples/gui/exp_controller_support.py | 118 ++++++++ arcade/gui/experimental/controller.py | 278 ++++++++++++++++++ 2 files changed, 396 insertions(+) create mode 100644 arcade/examples/gui/exp_controller_support.py create mode 100644 arcade/gui/experimental/controller.py diff --git a/arcade/examples/gui/exp_controller_support.py b/arcade/examples/gui/exp_controller_support.py new file mode 100644 index 000000000..09861990d --- /dev/null +++ b/arcade/examples/gui/exp_controller_support.py @@ -0,0 +1,118 @@ +from typing import Optional + +import arcade +from arcade import Texture +from arcade.gui import ( + UIAnchorLayout, + UIBoxLayout, + UIEvent, + UIFlatButton, + UIImage, + UIMouseFilterMixin, + UIOnClickEvent, + UIView, +) +from arcade.gui.experimental.controller import ( + UIControllerBridge, + UIControllerButtonPressEvent, + UIControllerDpadEvent, + UIFocusGroup, +) + + +class ControllerIndicator(UIAnchorLayout): + BLANK_TEX = Texture.create_empty("empty", (40, 40), arcade.color.TRANSPARENT_BLACK) + + def __init__(self): + super().__init__() + + self._indicator = self.add(UIImage(texture=self.BLANK_TEX), anchor_y="bottom", align_y=10) + + def on_event(self, event: UIEvent) -> Optional[bool]: + if isinstance(event, UIControllerButtonPressEvent): + self._indicator.texture = arcade.load_texture( + f":resources:onscreen_controls/flat_dark/{event.button}.png" + ) + arcade.unschedule(self.reset) + arcade.schedule_once(self.reset, 0.5) + elif isinstance(event, UIControllerDpadEvent): + tex_map = { + (1, 0): "right", + (-1, 0): "left", + (0, 1): "up", + (0, -1): "down", + } + + if event.vector in tex_map: + self._indicator.texture = arcade.load_texture( + f":resources:onscreen_controls/flat_dark/{tex_map[event.vector]}.png" + ) + arcade.unschedule(self.reset) + arcade.schedule_once(self.reset, 0.5) + + return super().on_event(event) + + def reset(self, *_): + print("Reset") + self._indicator.texture = self.BLANK_TEX + self.trigger_full_render() + + +class ControllerModal(UIMouseFilterMixin, UIFocusGroup): + def __init__(self): + super().__init__(size_hint=(0.8, 0.8)) + self.with_background(color=arcade.uicolor.DARK_BLUE_MIDNIGHT_BLUE) + + root = self.add(UIBoxLayout(space_between=10)) + + root.add(UIFlatButton(text="Modal Button 1")) + root.add(UIFlatButton(text="Modal Button 2")) + root.add(UIFlatButton(text="Modal Button 3")) + root.add(UIFlatButton(text="Close")).on_click = self.close + + self.detect_focusable_widgets() + + def on_event(self, event): + if super().on_event(event): + return True + + if isinstance(event, UIControllerButtonPressEvent): + if event.button == "b": + self.close(None) + return True + + return False + + def close(self, event): + print("Close") + # self.trigger_full_render() + self.trigger_full_render() + self.parent.remove(self) + + +class MyView(UIView): + def __init__(self): + super().__init__() + arcade.set_background_color(arcade.color.AMAZON) + + self.controller_bridge = UIControllerBridge(self.ui) + + self.root = self.add_widget(ControllerIndicator()) + self.root = self.root.add(UIFocusGroup()) + box = self.root.add(UIBoxLayout(space_between=10), anchor_x="left") + + box.add(UIFlatButton(text="Button 1")).on_click = self.on_button_click + box.add(UIFlatButton(text="Button 2")).on_click = self.on_button_click + box.add(UIFlatButton(text="Button 3")).on_click = self.on_button_click + + self.root.detect_focusable_widgets() + + def on_button_click(self, event: UIOnClickEvent): + print("Button clicked") + self.root.add(ControllerModal()) + + +if __name__ == "__main__": + window = arcade.Window(title="Controller UI Example") + window.show_view(MyView()) + arcade.run() diff --git a/arcade/gui/experimental/controller.py b/arcade/gui/experimental/controller.py new file mode 100644 index 000000000..7d20c208d --- /dev/null +++ b/arcade/gui/experimental/controller.py @@ -0,0 +1,278 @@ +from dataclasses import dataclass +from typing import Optional + +from pyglet.event import EVENT_HANDLED, EVENT_UNHANDLED +from pyglet.input import Controller +from pyglet.math import Vec2 + +import arcade +from arcade import ControllerManager, MOUSE_BUTTON_LEFT +from arcade.gui import ( + ListProperty, + Property, + Surface, + UIAnchorLayout, + UIEvent, + UIInteractiveWidget, + UIManager, + UIMousePressEvent, + UIMouseReleaseEvent, + UIWidget, + bind, +) + + +@dataclass +class UIControllerEvent(UIEvent): + """Base class for all UI controller events. + + Args: + source: The controller that triggered the event. + """ + + +@dataclass +class UIControllerStickEvent(UIControllerEvent): + """Triggered when a controller stick is moved. + + Args: + name: The name of the stick. + vector: The value of the stick. + """ + + name: str + vector: Vec2 + + +@dataclass +class UIControllerTriggerEvent(UIControllerEvent): + """Triggered when a controller trigger is moved. + + Args: + name: The name of the trigger. + value: The value of the trigger. + """ + + name: str + value: float + + +@dataclass +class UIControllerButtonPressEvent(UIControllerEvent): + """Triggered when a controller button is pressed. + + Args: + button: The name of the button. + """ + + button: str + + +@dataclass +class UIControllerButtonReleaseEvent(UIControllerEvent): + """Triggered when a controller button is released. + + Args: + button: The name of the button. + """ + + button: str + + +@dataclass +class UIControllerDpadEvent(UIControllerEvent): + """Triggered when a controller dpad is moved. + + Args: + vector: The value of the dpad. + """ + + vector: Vec2 + + +class ControllerListener: + """Interface for listening to controller events""" + + def on_stick_motion(self, controller: Controller, name: str, value: Vec2): + pass + + def on_trigger_motion(self, controller: Controller, name: str, value: float): + pass + + def on_button_press(self, controller: Controller, button_name: str): + pass + + def on_button_release(self, controller: Controller, button_name: str): + pass + + def on_dpad_motion(self, controller: Controller, value: Vec2): + pass + + +class UIControllerBridge(ControllerListener): + """Translates controller events to UIEvents and passes them to the UIManager + + Controller events are not consumed by the UIControllerBridge, + so they can be used by other systems. + + #TODO change this + This implicates, that the UIControllerBridge should be the first listener in the chain and + that other systems should be aware, when not to act on events (like when the UI is active). + """ + + def __init__(self, ui: UIManager): + self.ui = ui + self.cm = ControllerManager() + + self.cm.push_handlers(self) + # bind to existing controllers + for controller in self.cm.get_controllers(): + print("Controller connected", controller) + self.on_connect(controller) + + def on_connect(self, controller: Controller): + controller.push_handlers(self) + controller.open() + + def on_disconnect(self, controller: Controller): + controller.remove_handlers(self) + controller.close() + + # Controller event mapping + def on_stick_motion(self, controller: Controller, name, value): + self.ui.dispatch_ui_event(UIControllerStickEvent(controller, name, value)) + + def on_trigger_motion(self, controller: Controller, name, value): + self.ui.dispatch_ui_event(UIControllerTriggerEvent(controller, name, value)) + + def on_button_press(self, controller: Controller, button): + self.ui.dispatch_ui_event(UIControllerButtonPressEvent(controller, button)) + + def on_button_release(self, controller: Controller, button): + self.ui.dispatch_ui_event(UIControllerButtonReleaseEvent(controller, button)) + + def on_dpad_motion(self, controller: Controller, value): + self.ui.dispatch_ui_event(UIControllerDpadEvent(controller, value)) + + +class UIFocusGroup(UIAnchorLayout): + """A group of widgets that can be focused. + + UIFocusGroup maintains two lists of widgets: + - The list of focusable widgets. + - The list of widgets in. + + Use detect_focusable_widgets to automatically detect focusable widgets + or add_widget to add them manually. + + """ + + _widgets = ListProperty[UIWidget]() + _focused = Property(0) + + def __init__(self, size_hint=(1, 1), **kwargs): + super().__init__(size_hint=size_hint, **kwargs) + + bind(self, "_focused", self.trigger_full_render) + bind(self, "_widgets", self.trigger_full_render) + + def on_event(self, event: UIEvent) -> Optional[bool]: + + if super().on_event(event): + return EVENT_HANDLED + + if isinstance(event, UIControllerDpadEvent): + if event.vector.x == 1 or event.vector.y == -1: + self.focus_next() + return EVENT_HANDLED + elif event.vector.x == -1 or event.vector.y == 1: + self.focus_previous() + return EVENT_HANDLED + + elif isinstance(event, UIControllerButtonPressEvent): + if event.button == "a": + self.start_interaction() + return EVENT_HANDLED + elif isinstance(event, UIControllerButtonReleaseEvent): + if event.button == "a": + self.end_interaction() + return EVENT_HANDLED + + return EVENT_UNHANDLED + + def add_widget(self, widget): + self._widgets.append(widget) + + @classmethod + def _walk_widgets(cls, root: UIWidget): + for child in reversed(root.children): + yield child + yield from cls._walk_widgets(child) + + def detect_focusable_widgets(self, root: UIWidget = None): + """Automatically detect focusable widgets.""" + if root is None: + root = self + + widgets = self._walk_widgets(root) + + focusable_widgets = [] + for widget in reversed(list(widgets)): + if self.is_focusable(widget): + focusable_widgets.append(widget) + + self._widgets = focusable_widgets + + def focus_next(self): + self._focused += 1 + if self._focused >= len(self._widgets): + self._focused = 0 + + def focus_previous(self): + self._focused -= 1 + if self._focused < 0: + self._focused = len(self._widgets) - 1 + + def start_interaction(self): + widget = self._widgets[self._focused] + + if isinstance(widget, UIInteractiveWidget): + widget.dispatch_ui_event( + UIMousePressEvent( + source=self, + x=widget.rect.center_x, + y=widget.rect.center_y, + button=MOUSE_BUTTON_LEFT, + modifiers=0, + ) + ) + else: + print("Cannot interact widget") + + def end_interaction(self): + widget = self._widgets[self._focused] + + if isinstance(widget, UIInteractiveWidget): + widget.dispatch_ui_event( + UIMouseReleaseEvent( + source=self, + x=widget.rect.center_x, + y=widget.rect.center_y, + button=MOUSE_BUTTON_LEFT, + modifiers=0, + ) + ) + + # TODO render after children rendered + def do_render(self, surface: Surface): + surface.limit(None) + widget = self._widgets[self._focused] + arcade.draw_rect_outline( + rect=widget.rect, + color=arcade.color.WHITE, + border_width=2, + ) + + @staticmethod + def is_focusable(widget): + return isinstance(widget, UIInteractiveWidget) From fe0254a355c1ac9bdd2b3221ea367edd001da4d1 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Fri, 14 Feb 2025 21:23:03 +0100 Subject: [PATCH 02/34] experimental controller support incl inventory example --- arcade/examples/gui/exp_controller_support.py | 101 ++++- .../gui/exp_controller_support_grid.py | 88 ++++ arcade/examples/gui/exp_inventory_demo.py | 396 ++++++++++++++++++ arcade/gui/experimental/controller.py | 177 ++------ arcade/gui/experimental/focus.py | 297 +++++++++++++ .../input_prompt/xbox/controller_xbox360.png | Bin 0 -> 979 bytes .../xbox/controller_xbox_adaptive.png | Bin 0 -> 614 bytes .../input_prompt/xbox/controller_xboxone.png | Bin 0 -> 927 bytes .../xbox/controller_xboxseries.png | Bin 0 -> 970 bytes .../input_prompt/xbox/xbox_button_a.png | Bin 0 -> 982 bytes .../xbox/xbox_button_a_outline.png | Bin 0 -> 1269 bytes .../input_prompt/xbox/xbox_button_b.png | Bin 0 -> 900 bytes .../xbox/xbox_button_b_outline.png | Bin 0 -> 1196 bytes .../input_prompt/xbox/xbox_button_back.png | Bin 0 -> 860 bytes .../xbox/xbox_button_back_icon.png | Bin 0 -> 673 bytes .../xbox/xbox_button_back_icon_outline.png | Bin 0 -> 882 bytes .../xbox/xbox_button_back_outline.png | Bin 0 -> 1059 bytes .../input_prompt/xbox/xbox_button_color_a.png | Bin 0 -> 982 bytes .../xbox/xbox_button_color_a_outline.png | Bin 0 -> 1269 bytes .../input_prompt/xbox/xbox_button_color_b.png | Bin 0 -> 900 bytes .../xbox/xbox_button_color_b_outline.png | Bin 0 -> 1191 bytes .../input_prompt/xbox/xbox_button_color_x.png | Bin 0 -> 1036 bytes .../xbox/xbox_button_color_x_outline.png | Bin 0 -> 1336 bytes .../input_prompt/xbox/xbox_button_color_y.png | Bin 0 -> 941 bytes .../xbox/xbox_button_color_y_outline.png | Bin 0 -> 1253 bytes .../input_prompt/xbox/xbox_button_menu.png | Bin 0 -> 774 bytes .../xbox/xbox_button_menu_outline.png | Bin 0 -> 1086 bytes .../input_prompt/xbox/xbox_button_share.png | Bin 0 -> 658 bytes .../xbox/xbox_button_share_outline.png | Bin 0 -> 880 bytes .../input_prompt/xbox/xbox_button_start.png | Bin 0 -> 879 bytes .../xbox/xbox_button_start_icon.png | Bin 0 -> 666 bytes .../xbox/xbox_button_start_icon_outline.png | Bin 0 -> 885 bytes .../xbox/xbox_button_start_outline.png | Bin 0 -> 1077 bytes .../input_prompt/xbox/xbox_button_view.png | Bin 0 -> 846 bytes .../xbox/xbox_button_view_outline.png | Bin 0 -> 1168 bytes .../input_prompt/xbox/xbox_button_x.png | Bin 0 -> 1037 bytes .../xbox/xbox_button_x_outline.png | Bin 0 -> 1336 bytes .../input_prompt/xbox/xbox_button_y.png | Bin 0 -> 941 bytes .../xbox/xbox_button_y_outline.png | Bin 0 -> 1253 bytes .../assets/input_prompt/xbox/xbox_dpad.png | Bin 0 -> 351 bytes .../input_prompt/xbox/xbox_dpad_all.png | Bin 0 -> 351 bytes .../input_prompt/xbox/xbox_dpad_down.png | Bin 0 -> 416 bytes .../xbox/xbox_dpad_down_outline.png | Bin 0 -> 400 bytes .../xbox/xbox_dpad_horizontal.png | Bin 0 -> 435 bytes .../xbox/xbox_dpad_horizontal_outline.png | Bin 0 -> 377 bytes .../input_prompt/xbox/xbox_dpad_left.png | Bin 0 -> 434 bytes .../xbox/xbox_dpad_left_outline.png | Bin 0 -> 389 bytes .../input_prompt/xbox/xbox_dpad_none.png | Bin 0 -> 398 bytes .../input_prompt/xbox/xbox_dpad_right.png | Bin 0 -> 427 bytes .../xbox/xbox_dpad_right_outline.png | Bin 0 -> 391 bytes .../input_prompt/xbox/xbox_dpad_round.png | Bin 0 -> 1032 bytes .../input_prompt/xbox/xbox_dpad_round_all.png | Bin 0 -> 1111 bytes .../xbox/xbox_dpad_round_down.png | Bin 0 -> 1112 bytes .../xbox/xbox_dpad_round_horizontal.png | Bin 0 -> 1118 bytes .../xbox/xbox_dpad_round_left.png | Bin 0 -> 1125 bytes .../xbox/xbox_dpad_round_right.png | Bin 0 -> 1117 bytes .../input_prompt/xbox/xbox_dpad_round_up.png | Bin 0 -> 1128 bytes .../xbox/xbox_dpad_round_vertical.png | Bin 0 -> 1128 bytes .../assets/input_prompt/xbox/xbox_dpad_up.png | Bin 0 -> 436 bytes .../xbox/xbox_dpad_up_outline.png | Bin 0 -> 394 bytes .../input_prompt/xbox/xbox_dpad_vertical.png | Bin 0 -> 422 bytes .../xbox/xbox_dpad_vertical_outline.png | Bin 0 -> 376 bytes .../assets/input_prompt/xbox/xbox_guide.png | Bin 0 -> 1193 bytes .../input_prompt/xbox/xbox_guide_outline.png | Bin 0 -> 1597 bytes .../assets/input_prompt/xbox/xbox_lb.png | Bin 0 -> 589 bytes .../input_prompt/xbox/xbox_lb_outline.png | Bin 0 -> 718 bytes .../assets/input_prompt/xbox/xbox_ls.png | Bin 0 -> 971 bytes .../input_prompt/xbox/xbox_ls_outline.png | Bin 0 -> 1280 bytes .../assets/input_prompt/xbox/xbox_lt.png | Bin 0 -> 533 bytes .../input_prompt/xbox/xbox_lt_outline.png | Bin 0 -> 722 bytes .../assets/input_prompt/xbox/xbox_rb.png | Bin 0 -> 684 bytes .../input_prompt/xbox/xbox_rb_outline.png | Bin 0 -> 800 bytes .../assets/input_prompt/xbox/xbox_rs.png | Bin 0 -> 1065 bytes .../input_prompt/xbox/xbox_rs_outline.png | Bin 0 -> 1354 bytes .../assets/input_prompt/xbox/xbox_rt.png | Bin 0 -> 673 bytes .../input_prompt/xbox/xbox_rt_outline.png | Bin 0 -> 824 bytes .../assets/input_prompt/xbox/xbox_stick_l.png | Bin 0 -> 1228 bytes .../input_prompt/xbox/xbox_stick_l_down.png | Bin 0 -> 1329 bytes .../xbox/xbox_stick_l_horizontal.png | Bin 0 -> 1257 bytes .../input_prompt/xbox/xbox_stick_l_left.png | Bin 0 -> 1263 bytes .../input_prompt/xbox/xbox_stick_l_press.png | Bin 0 -> 1309 bytes .../input_prompt/xbox/xbox_stick_l_right.png | Bin 0 -> 1248 bytes .../input_prompt/xbox/xbox_stick_l_up.png | Bin 0 -> 1319 bytes .../xbox/xbox_stick_l_vertical.png | Bin 0 -> 1405 bytes .../assets/input_prompt/xbox/xbox_stick_r.png | Bin 0 -> 1286 bytes .../input_prompt/xbox/xbox_stick_r_down.png | Bin 0 -> 1384 bytes .../xbox/xbox_stick_r_horizontal.png | Bin 0 -> 1324 bytes .../input_prompt/xbox/xbox_stick_r_left.png | Bin 0 -> 1317 bytes .../input_prompt/xbox/xbox_stick_r_press.png | Bin 0 -> 1364 bytes .../input_prompt/xbox/xbox_stick_r_right.png | Bin 0 -> 1321 bytes .../input_prompt/xbox/xbox_stick_r_up.png | Bin 0 -> 1370 bytes .../xbox/xbox_stick_r_vertical.png | Bin 0 -> 1465 bytes .../input_prompt/xbox/xbox_stick_side_l.png | Bin 0 -> 565 bytes .../input_prompt/xbox/xbox_stick_side_r.png | Bin 0 -> 654 bytes .../input_prompt/xbox/xbox_stick_top_l.png | Bin 0 -> 1268 bytes .../input_prompt/xbox/xbox_stick_top_r.png | Bin 0 -> 1359 bytes 96 files changed, 886 insertions(+), 173 deletions(-) create mode 100644 arcade/examples/gui/exp_controller_support_grid.py create mode 100644 arcade/examples/gui/exp_inventory_demo.py create mode 100644 arcade/gui/experimental/focus.py create mode 100755 arcade/resources/assets/input_prompt/xbox/controller_xbox360.png create mode 100755 arcade/resources/assets/input_prompt/xbox/controller_xbox_adaptive.png create mode 100755 arcade/resources/assets/input_prompt/xbox/controller_xboxone.png create mode 100755 arcade/resources/assets/input_prompt/xbox/controller_xboxseries.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_a.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_a_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_b.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_b_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_back.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_back_icon.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_back_icon_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_back_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_a.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_a_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_b.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_b_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_x.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_x_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_y.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_y_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_menu.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_menu_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_share.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_share_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_start.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_start_icon.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_start_icon_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_start_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_view.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_view_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_x.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_x_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_y.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_y_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_all.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_down.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_down_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_horizontal.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_horizontal_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_left.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_left_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_none.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_right.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_right_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_all.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_down.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_horizontal.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_left.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_right.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_up.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_vertical.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_up.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_up_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_vertical.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_vertical_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_guide.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_guide_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_lb.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_lb_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_ls.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_ls_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_lt.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_lt_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_rb.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_rb_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_rs.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_rs_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_rt.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_rt_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l_down.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l_horizontal.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l_left.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l_press.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l_right.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l_up.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l_vertical.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r_down.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r_horizontal.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r_left.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r_press.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r_right.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r_up.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r_vertical.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_side_l.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_side_r.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_top_l.png create mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_top_r.png diff --git a/arcade/examples/gui/exp_controller_support.py b/arcade/examples/gui/exp_controller_support.py index 09861990d..d2f97cec5 100644 --- a/arcade/examples/gui/exp_controller_support.py +++ b/arcade/examples/gui/exp_controller_support.py @@ -14,48 +14,107 @@ ) from arcade.gui.experimental.controller import ( UIControllerBridge, + UIControllerButtonEvent, UIControllerButtonPressEvent, UIControllerDpadEvent, - UIFocusGroup, + UIControllerEvent, + UIControllerStickEvent, + UIControllerTriggerEvent, ) +from arcade.gui.experimental.focus import UIFocusGroup +from arcade.types import Color class ControllerIndicator(UIAnchorLayout): + """ + A widget that displays the last controller input. + """ + BLANK_TEX = Texture.create_empty("empty", (40, 40), arcade.color.TRANSPARENT_BLACK) + TEXTURE_CACHE = {} def __init__(self): super().__init__() self._indicator = self.add(UIImage(texture=self.BLANK_TEX), anchor_y="bottom", align_y=10) + self._indicator.with_background(color=Color(0, 0, 0, 0)) + self._indicator._strong_background = True + + @classmethod + def get_texture(cls, path: str) -> Texture: + if path not in cls.TEXTURE_CACHE: + cls.TEXTURE_CACHE[path] = arcade.load_texture(path) + return cls.TEXTURE_CACHE[path] + + @classmethod + def input_prompts(cls, event: UIControllerEvent) -> Texture | None: + if isinstance(event, UIControllerButtonEvent): + match event.button: + case "a": + return cls.get_texture(":resources:input_prompt/xbox/xbox_button_a.png") + case "b": + return cls.get_texture(":resources:input_prompt/xbox/xbox_button_b.png") + case "x": + return cls.get_texture(":resources:input_prompt/xbox/xbox_button_x.png") + case "y": + return cls.get_texture(":resources:input_prompt/xbox/xbox_button_y.png") + case "rightshoulder": + return cls.get_texture(":resources:input_prompt/xbox/xbox_rb.png") + case "leftshoulder": + return cls.get_texture(":resources:input_prompt/xbox/xbox_lb.png") + case "start": + return cls.get_texture(":resources:input_prompt/xbox/xbox_button_start.png") + case "back": + return cls.get_texture(":resources:input_prompt/xbox/xbox_button_back.png") + + if isinstance(event, UIControllerTriggerEvent): + match event.name: + case "lefttrigger": + return cls.get_texture(":resources:input_prompt/xbox/xbox_lt.png") + case "righttrigger": + return cls.get_texture(":resources:input_prompt/xbox/xbox_rt.png") + + if isinstance(event, UIControllerDpadEvent): + match event.vector: + case (1, 0): + return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_right.png") + case (-1, 0): + return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_left.png") + case (0, 1): + return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_up.png") + case (0, -1): + return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_down.png") + + if isinstance(event, UIControllerStickEvent) and event.vector.length() > 0.2: + stick = "l" if event.name == "leftstick" else "r" + + # map atan2(y, x) to direction string (up, down, left, right) + heading = event.vector.heading() + if 0.785 > heading > -0.785: + return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_right.png") + elif 0.785 < heading < 2.356: + return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_up.png") + elif heading > 2.356 or heading < -2.356: + return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_left.png") + elif -2.356 < heading < -0.785: + return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_down.png") + + return None def on_event(self, event: UIEvent) -> Optional[bool]: - if isinstance(event, UIControllerButtonPressEvent): - self._indicator.texture = arcade.load_texture( - f":resources:onscreen_controls/flat_dark/{event.button}.png" - ) - arcade.unschedule(self.reset) - arcade.schedule_once(self.reset, 0.5) - elif isinstance(event, UIControllerDpadEvent): - tex_map = { - (1, 0): "right", - (-1, 0): "left", - (0, 1): "up", - (0, -1): "down", - } - - if event.vector in tex_map: - self._indicator.texture = arcade.load_texture( - f":resources:onscreen_controls/flat_dark/{tex_map[event.vector]}.png" - ) + if isinstance(event, UIControllerEvent): + input_texture = self.input_prompts(event) + + if input_texture: + self._indicator.texture = input_texture + arcade.unschedule(self.reset) arcade.schedule_once(self.reset, 0.5) return super().on_event(event) def reset(self, *_): - print("Reset") self._indicator.texture = self.BLANK_TEX - self.trigger_full_render() class ControllerModal(UIMouseFilterMixin, UIFocusGroup): diff --git a/arcade/examples/gui/exp_controller_support_grid.py b/arcade/examples/gui/exp_controller_support_grid.py new file mode 100644 index 000000000..5cddcae31 --- /dev/null +++ b/arcade/examples/gui/exp_controller_support_grid.py @@ -0,0 +1,88 @@ +from typing import Dict, Tuple + +import arcade +from arcade.examples.gui.exp_controller_support import ControllerIndicator +from arcade.gui import ( + UIFlatButton, + UIGridLayout, + UIView, +) +from arcade.gui.experimental.controller import ( + UIControllerBridge, +) +from arcade.gui.experimental.focus import Focusable, UIFocusGroup + + +class FocusableButton(Focusable, UIFlatButton): + pass + + +def setup_grid_focus_transition(grid: Dict[Tuple[int, int], Focusable]): + """Setup focus transition in grid. + + Connect focus transition between `Focusable` in grid. + + Args: + grid: Dict[Tuple[int, int], Focusable]: grid of Focusable widgets. + key represents position in grid (x,y) + + """ + + cols = max(x for x, y in grid.keys()) + 1 + rows = max(y for x, y in grid.keys()) + 1 + for c in range(cols): + for r in range(rows): + btn = grid.get((c, r)) + if btn is None or not isinstance(btn, Focusable): + continue + + if c > 0: + btn.neighbor_left = grid.get((c - 1, r)) + else: + btn.neighbor_left = grid.get((cols - 1, r)) + + if c < cols - 1: + btn.neighbor_right = grid.get((c + 1, r)) + else: + btn.neighbor_right = grid.get((0, r)) + + if r > 0: + btn.neighbor_up = grid.get((c, r - 1)) + else: + btn.neighbor_up = grid.get((c, rows - 1)) + + if r < rows - 1: + btn.neighbor_down = grid.get((c, r + 1)) + else: + btn.neighbor_down = grid.get((c, 0)) + + +class MyView(UIView): + def __init__(self): + super().__init__() + arcade.set_background_color(arcade.color.AMAZON) + + self.controller_bridge = UIControllerBridge(self.ui) + + self.root = self.add_widget(ControllerIndicator()) + self.root = self.root.add(UIFocusGroup()) + grid = self.root.add( + UIGridLayout(column_count=3, row_count=3, vertical_spacing=10, horizontal_spacing=10) + ) + + _grid = {} + for i in range(9): + btn = FocusableButton(text=f"Button {i}") + _grid[(i % 3, i // 3)] = btn + grid.add(btn, column=i % 3, row=i // 3) + + # connect focus transition in grid + setup_grid_focus_transition(_grid) + + self.root.detect_focusable_widgets() + + +if __name__ == "__main__": + window = arcade.Window(title="Controller UI Example") + window.show_view(MyView()) + arcade.run() diff --git a/arcade/examples/gui/exp_inventory_demo.py b/arcade/examples/gui/exp_inventory_demo.py new file mode 100644 index 000000000..c91fad1dd --- /dev/null +++ b/arcade/examples/gui/exp_inventory_demo.py @@ -0,0 +1,396 @@ +""" + +Example of a full functional inventory system. + +This example demonstrates how to create a simple inventory system. + +Main features are: +- Inventory slots +- Equipment slots +- Move items between slots +- Controller support + +""" + +# TODO: Drag and Drop + +from typing import List + +import pyglet.font +from pyglet.gl import GL_NEAREST + +import arcade +from arcade import Rect, open_window +from arcade.examples.gui.exp_controller_support_grid import ( + ControllerIndicator, + setup_grid_focus_transition, +) +from arcade.gui import ( + Property, + Surface, + UIAnchorLayout, + UIBoxLayout, + UIFlatButton, + UIGridLayout, + UILabel, + UIOnClickEvent, + UIView, + UIWidget, + bind, +) +from arcade.gui.experimental.controller import UIControllerBridge +from arcade.gui.experimental.focus import Focusable, UIFocusGroup +from arcade.resources import load_kenney_fonts + + +class Item: + """Base class for all items.""" + + def __init__(self, symbol: str): + self.symbol = symbol + + +class Inventory: + """ + Basic inventory class. + + Contains items and manages items. + + + inventory = Inventory(10) + inventory.add(Item("🍎")) + inventory.add(Item("🍌")) + inventory.add(Item("🍇")) + + + for item in inventory: + print(item.symbol) + + inventory.remove(inventory[0]) + """ + + def __init__(self, capacity: int): + self._items: List[Item | None] = [None for _ in range(capacity)] + self.capacity = capacity + + def add(self, item: Item): + empty_slot = None + for i, slot in enumerate(self._items): + if slot is None: + empty_slot = i + break + + if empty_slot is not None: + self._items[empty_slot] = item + else: + raise ValueError("Inventory is full.") + + def is_full(self): + return len(self._items) == self.capacity + + def remove(self, item: Item): + for i, slot in enumerate(self._items): + if slot == item: + self._items[i] = None + return + + def __getitem__(self, index: int): + return self._items[index] + + def __setitem__(self, index: int, value: Item): + self._items[index] = value + + def __iter__(self): + yield from self._items + + +class Equipment(Inventory): + """Equipment inventory. + + Contains three slots for head, chest and legs. + """ + + def __init__(self): + super().__init__(3) + + @property + def head(self) -> Item: + return self[0] + + @head.setter + def head(self, value): + self[0] = value + + @property + def chest(self) -> Item: + return self[1] + + @chest.setter + def chest(self, value): + self[1] = value + + @property + def legs(self) -> Item: + return self[2] + + @legs.setter + def legs(self, value): + self[2] = value + + +class InventorySlotUI(Focusable, UIFlatButton): + """Represents a single inventory slot. + The slot accesses a specific index in the inventory. + + Emits an on_click event. + """ + + def __init__(self, inventory: Inventory, index: int, **kwargs): + super().__init__(size_hint=(1, 1), **kwargs) + self.ui_label.update_font(font_size=24) + self._inventory = inventory + self._index = index + + item = inventory[index] + if item: + self.text = item.symbol + + @property + def item(self) -> Item | None: + return self._inventory[self._index] + + @item.setter + def item(self, value): + self._inventory[self._index] = value + self._on_item_change() + + def _on_item_change(self, *args): + if self.item: + self.text = self.item.symbol + else: + self.text = "" + + +class EquipmentSlotUI(InventorySlotUI): + pass + + +class InventoryUI(UIGridLayout): + """Manages inventory slots. + + Emits an `on_slot_clicked(slot)` event when a slot is clicked. + + """ + + def __init__(self, inventory: Inventory, **kwargs): + super().__init__( + size_hint=(0.7, 1), + column_count=6, + row_count=5, + align_vertical="center", + align_horizontal="center", + vertical_spacing=10, + horizontal_spacing=10, + **kwargs, + ) + self.with_padding(all=10) + self.with_border(color=arcade.color.WHITE, width=2) + + self.inventory = inventory + self.grid = {} + + for i, item in enumerate(inventory): + slot = InventorySlotUI(inventory, i) + # fill left to right, bottom to top (6x5 grid) + self.add(slot, column=i % 6, row=i // 6) + self.grid[(i % 6, i // 6)] = slot + slot.on_click = self._on_slot_click + + InventoryUI.register_event_type("on_slot_clicked") + + def _on_slot_click(self, event: UIOnClickEvent): + # propagate slot click event to parent + self.dispatch_event("on_slot_clicked", event.source) + + def on_slot_clicked(self, event: UIOnClickEvent): + pass + + +class EquipmentUI(UIBoxLayout): + """Contains three slots for equipment items. + + - Head + - Chest + - Legs + + Emits an `on_slot_clicked(slot)` event when a slot is clicked. + + """ + + def __init__(self, **kwargs): + super().__init__(size_hint=(0.3, 1), space_between=10, **kwargs) + self.with_padding(all=20) + self.with_border(color=arcade.color.WHITE, width=2) + + equipment = Equipment() + + self.head_slot = self.add(EquipmentSlotUI(equipment, 0)) + self.head_slot.on_click = lambda _: self.dispatch_event("on_slot_clicked", self.head_slot) + + self.chest_slot = self.add(EquipmentSlotUI(equipment, 1)) + self.chest_slot.on_click = lambda _: self.dispatch_event("on_slot_clicked", self.chest_slot) + + self.legs_slot = self.add(EquipmentSlotUI(equipment, 2)) + self.legs_slot.on_click = lambda _: self.dispatch_event("on_slot_clicked", self.legs_slot) + + EquipmentUI.register_event_type("on_slot_clicked") + + +class ActiveSlotTrackerMixin(UIWidget): + """ + Mixin class to track the active slot. + """ + + active_slot = Property(None) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + bind(self, "active_slot", self.trigger_render) + + def do_render(self, surface: Surface): + surface.limit(None) + if self.active_slot: + rect: Rect = self.active_slot.rect + + rect = rect.resize(*(rect.size + (2, 2))) + arcade.draw_rect_outline(rect, arcade.uicolor.RED_ALIZARIN, 2) + + return super().do_render(surface) + + def on_slot_clicked(self, clicked_slot: InventorySlotUI): + if self.active_slot: + # disable active slot + if clicked_slot == self.active_slot: + self.active_slot = None + return + + else: + # swap items + src_item = self.active_slot.item + dst_item = clicked_slot.item + + self.active_slot.item = dst_item + clicked_slot.item = src_item + + self.active_slot = None + return + + else: + # activate slot if contains item + if clicked_slot.item: + self.active_slot = clicked_slot + + +class InventoryModal(ActiveSlotTrackerMixin, UIFocusGroup, UIAnchorLayout): + def __init__(self, inventory: Inventory, **kwargs): + super().__init__(size_hint=(0.8, 0.8), **kwargs) + self.with_padding(all=10) + self.with_background(color=arcade.uicolor.GREEN_GREEN_SEA) + self._debug = True + + self.add( + UILabel(text="Inventory", font_size=20, font_name="Kenney Blocks", bold=True), + anchor_y="top", + ) + + content = UIBoxLayout(size_hint=(1, 0.9), vertical=False, space_between=10) + self.add(content, anchor_y="bottom") + + inv_ui = content.add(InventoryUI(inventory)) + inv_ui.on_slot_clicked = self.on_slot_clicked + + eq_ui = content.add(EquipmentUI()) + eq_ui.on_slot_clicked = self.on_slot_clicked + + # prepare focusable widgets + widget_grid = inv_ui.grid + setup_grid_focus_transition(widget_grid) # setup default transitions in a grid + + # add transitions to equipment slots + cols = max(x for x, y in widget_grid.keys()) + rows = max(y for x, y in widget_grid.keys()) + + equipment_slots = [eq_ui.head_slot, eq_ui.chest_slot, eq_ui.legs_slot] + + # connect inventory slots with equipment slots + slots_to_eq_ratio = (rows + 1) / len(equipment_slots) + for i in range(rows + 1): + eq_index = int(i // slots_to_eq_ratio) + eq_slot = equipment_slots[eq_index] + + inv_slot = widget_grid[(cols, i)] + + inv_slot.neighbor_right = eq_slot + eq_slot.neighbor_left = inv_slot + + # focusable widgets + self.detect_focusable_widgets() + + # close button, not focusable (controller use B to close) + close_button = self.add( + # todo: find out why X is not in center + UIFlatButton(text="X", width=40, height=40), + anchor_x="right", + anchor_y="top", + ) + close_button.on_click = lambda _: self.close() + + def close(self): + self.trigger_full_render() + self.parent.remove(self) + + +class MyView(UIView): + def __init__(self): + super().__init__() + + self.cb = UIControllerBridge(self.ui) + + self.background_color = arcade.color.BLACK + + self.inventory = Inventory(30) + + self.inventory.add(Item("🍎")) + self.inventory.add(Item("🍌")) + self.inventory.add(Item("🍇")) + + self.root = self.add_widget(UIAnchorLayout()) + self.add_widget(ControllerIndicator()) + + self.show_inventory() + + def show_inventory(self): + self.root.add(InventoryModal(self.inventory)) + + def on_key_press(self, symbol: int, modifiers: int) -> bool | None: + if symbol == arcade.key.I: + print("Show inventory") + for i, item in enumerate(self.inventory): + print(i, item.symbol if item else "-") + return True + + def on_draw_before_ui(self): + pass + + +if __name__ == "__main__": + # pixelate the font + pyglet.font.base.Font.texture_min_filter = GL_NEAREST + pyglet.font.base.Font.texture_mag_filter = GL_NEAREST + + load_kenney_fonts() + + open_window(window_title="Minimal example", width=1280, height=720, resizable=True).show_view( + MyView() + ) + arcade.run() diff --git a/arcade/gui/experimental/controller.py b/arcade/gui/experimental/controller.py index 7d20c208d..040f12e43 100644 --- a/arcade/gui/experimental/controller.py +++ b/arcade/gui/experimental/controller.py @@ -1,24 +1,12 @@ from dataclasses import dataclass -from typing import Optional -from pyglet.event import EVENT_HANDLED, EVENT_UNHANDLED from pyglet.input import Controller from pyglet.math import Vec2 -import arcade -from arcade import ControllerManager, MOUSE_BUTTON_LEFT +from arcade import ControllerManager from arcade.gui import ( - ListProperty, - Property, - Surface, - UIAnchorLayout, UIEvent, - UIInteractiveWidget, UIManager, - UIMousePressEvent, - UIMouseReleaseEvent, - UIWidget, - bind, ) @@ -58,8 +46,8 @@ class UIControllerTriggerEvent(UIControllerEvent): @dataclass -class UIControllerButtonPressEvent(UIControllerEvent): - """Triggered when a controller button is pressed. +class UIControllerButtonEvent(UIControllerEvent): + """Triggered when a controller button used. Args: button: The name of the button. @@ -69,14 +57,21 @@ class UIControllerButtonPressEvent(UIControllerEvent): @dataclass -class UIControllerButtonReleaseEvent(UIControllerEvent): - """Triggered when a controller button is released. +class UIControllerButtonPressEvent(UIControllerButtonEvent): + """Triggered when a controller button is pressed. Args: button: The name of the button. """ - button: str + +@dataclass +class UIControllerButtonReleaseEvent(UIControllerButtonEvent): + """Triggered when a controller button is released. + + Args: + button: The name of the button. + """ @dataclass @@ -90,7 +85,7 @@ class UIControllerDpadEvent(UIControllerEvent): vector: Vec2 -class ControllerListener: +class _ControllerListener: """Interface for listening to controller events""" def on_stick_motion(self, controller: Controller, name: str, value: Vec2): @@ -109,13 +104,14 @@ def on_dpad_motion(self, controller: Controller, value: Vec2): pass -class UIControllerBridge(ControllerListener): - """Translates controller events to UIEvents and passes them to the UIManager +class UIControllerBridge(_ControllerListener): + """Translates controller events to UIEvents and passes them to the UIManager. + + Controller are automatically connected and disconnected. - Controller events are not consumed by the UIControllerBridge, - so they can be used by other systems. + Controller events are consumed by the UIControllerBridge, + if the UIEvent is consumed by the UIManager. - #TODO change this This implicates, that the UIControllerBridge should be the first listener in the chain and that other systems should be aware, when not to act on events (like when the UI is active). """ @@ -140,139 +136,16 @@ def on_disconnect(self, controller: Controller): # Controller event mapping def on_stick_motion(self, controller: Controller, name, value): - self.ui.dispatch_ui_event(UIControllerStickEvent(controller, name, value)) + return self.ui.dispatch_ui_event(UIControllerStickEvent(controller, name, value)) def on_trigger_motion(self, controller: Controller, name, value): - self.ui.dispatch_ui_event(UIControllerTriggerEvent(controller, name, value)) + return self.ui.dispatch_ui_event(UIControllerTriggerEvent(controller, name, value)) def on_button_press(self, controller: Controller, button): - self.ui.dispatch_ui_event(UIControllerButtonPressEvent(controller, button)) + return self.ui.dispatch_ui_event(UIControllerButtonPressEvent(controller, button)) def on_button_release(self, controller: Controller, button): - self.ui.dispatch_ui_event(UIControllerButtonReleaseEvent(controller, button)) + return self.ui.dispatch_ui_event(UIControllerButtonReleaseEvent(controller, button)) def on_dpad_motion(self, controller: Controller, value): - self.ui.dispatch_ui_event(UIControllerDpadEvent(controller, value)) - - -class UIFocusGroup(UIAnchorLayout): - """A group of widgets that can be focused. - - UIFocusGroup maintains two lists of widgets: - - The list of focusable widgets. - - The list of widgets in. - - Use detect_focusable_widgets to automatically detect focusable widgets - or add_widget to add them manually. - - """ - - _widgets = ListProperty[UIWidget]() - _focused = Property(0) - - def __init__(self, size_hint=(1, 1), **kwargs): - super().__init__(size_hint=size_hint, **kwargs) - - bind(self, "_focused", self.trigger_full_render) - bind(self, "_widgets", self.trigger_full_render) - - def on_event(self, event: UIEvent) -> Optional[bool]: - - if super().on_event(event): - return EVENT_HANDLED - - if isinstance(event, UIControllerDpadEvent): - if event.vector.x == 1 or event.vector.y == -1: - self.focus_next() - return EVENT_HANDLED - elif event.vector.x == -1 or event.vector.y == 1: - self.focus_previous() - return EVENT_HANDLED - - elif isinstance(event, UIControllerButtonPressEvent): - if event.button == "a": - self.start_interaction() - return EVENT_HANDLED - elif isinstance(event, UIControllerButtonReleaseEvent): - if event.button == "a": - self.end_interaction() - return EVENT_HANDLED - - return EVENT_UNHANDLED - - def add_widget(self, widget): - self._widgets.append(widget) - - @classmethod - def _walk_widgets(cls, root: UIWidget): - for child in reversed(root.children): - yield child - yield from cls._walk_widgets(child) - - def detect_focusable_widgets(self, root: UIWidget = None): - """Automatically detect focusable widgets.""" - if root is None: - root = self - - widgets = self._walk_widgets(root) - - focusable_widgets = [] - for widget in reversed(list(widgets)): - if self.is_focusable(widget): - focusable_widgets.append(widget) - - self._widgets = focusable_widgets - - def focus_next(self): - self._focused += 1 - if self._focused >= len(self._widgets): - self._focused = 0 - - def focus_previous(self): - self._focused -= 1 - if self._focused < 0: - self._focused = len(self._widgets) - 1 - - def start_interaction(self): - widget = self._widgets[self._focused] - - if isinstance(widget, UIInteractiveWidget): - widget.dispatch_ui_event( - UIMousePressEvent( - source=self, - x=widget.rect.center_x, - y=widget.rect.center_y, - button=MOUSE_BUTTON_LEFT, - modifiers=0, - ) - ) - else: - print("Cannot interact widget") - - def end_interaction(self): - widget = self._widgets[self._focused] - - if isinstance(widget, UIInteractiveWidget): - widget.dispatch_ui_event( - UIMouseReleaseEvent( - source=self, - x=widget.rect.center_x, - y=widget.rect.center_y, - button=MOUSE_BUTTON_LEFT, - modifiers=0, - ) - ) - - # TODO render after children rendered - def do_render(self, surface: Surface): - surface.limit(None) - widget = self._widgets[self._focused] - arcade.draw_rect_outline( - rect=widget.rect, - color=arcade.color.WHITE, - border_width=2, - ) - - @staticmethod - def is_focusable(widget): - return isinstance(widget, UIInteractiveWidget) + return self.ui.dispatch_ui_event(UIControllerDpadEvent(controller, value)) diff --git a/arcade/gui/experimental/focus.py b/arcade/gui/experimental/focus.py new file mode 100644 index 000000000..09e512f9b --- /dev/null +++ b/arcade/gui/experimental/focus.py @@ -0,0 +1,297 @@ +from typing import Optional + +from pyglet.event import EVENT_HANDLED, EVENT_UNHANDLED +from pyglet.math import Vec2 + +import arcade +from arcade import MOUSE_BUTTON_LEFT +from arcade.gui import ( + ListProperty, + Property, + Surface, + UIAnchorLayout, + UIEvent, + UIInteractiveWidget, + UIKeyPressEvent, + UIKeyReleaseEvent, + UIManager, + UIMousePressEvent, + UIMouseReleaseEvent, + UIWidget, + bind, +) +from arcade.gui.experimental.controller import ( + UIControllerButtonPressEvent, + UIControllerButtonReleaseEvent, + UIControllerDpadEvent, +) + + +class Focusable(UIWidget): + """ + A widget that can be focused and provides additional information about focus behavior. + + Attributes: + + neighbor_up: The widget above this widget. + neighbor_right: The widget right of this widget. + neighbor_down: The widget below this widget. + neighbor_left: The widget left of this widget. + + """ + + # todo set focused when focused + focused = Property(False) + + neighbor_up: UIWidget | None = None + neighbor_right: UIWidget | None = None + neighbor_down: UIWidget | None = None + neighbor_left: UIWidget | None = None + + @property + def ui(self) -> UIManager | None: + """The UIManager this widget is attached to.""" + w = self + while w.parent: + if isinstance(w.parent, UIManager): + return w.parent + w = self.parent + return None + + +class UIFocusGroup(UIAnchorLayout): + """A group of widgets that can be focused. + + UIFocusGroup maintains two lists of widgets: + - The list of focusable widgets. + - The list of widgets in. + + Use `detect_focusable_widgets()` to automatically detect focusable widgets + or add_widget to add them manually. + + The Group can be navigated with the keyboard or controller. + + - DPAD: Navigate between focusable widgets. (up, down, left, right) + - TAB: Navigate between focusable widgets. + - A Button or SPACE: Interact with the focused widget. + + """ + + _focusable_widgets = ListProperty[UIWidget]() + _focused = Property(0) + + _debug = Property(False) + + def __init__(self, size_hint=(1, 1), **kwargs): + super().__init__(size_hint=size_hint, **kwargs) + + bind(self, "_debug", self.trigger_full_render) + bind(self, "_focused", self.trigger_full_render) + bind(self, "_focusable_widgets", self.trigger_full_render) + + def on_event(self, event: UIEvent) -> Optional[bool]: + if super().on_event(event): + return EVENT_HANDLED + + if isinstance(event, UIKeyPressEvent): + if event.symbol == arcade.key.TAB: + if event.modifiers & arcade.key.MOD_SHIFT: + self.focus_previous() + else: + self.focus_next() + + return EVENT_HANDLED + + elif event.symbol == arcade.key.SPACE: + self.start_interaction() + return EVENT_HANDLED + + elif isinstance(event, UIKeyReleaseEvent): + if event.symbol == arcade.key.SPACE: + self.end_interaction() + return EVENT_HANDLED + + if isinstance(event, UIControllerDpadEvent): + if event.vector.x == 1: + self.focus_right() + return EVENT_HANDLED + + elif event.vector.y == 1: + self.focus_up() + return EVENT_HANDLED + + elif event.vector.x == -1: + self.focus_left() + return EVENT_HANDLED + + elif event.vector.y == -1: + self.focus_down() + return EVENT_HANDLED + + elif isinstance(event, UIControllerButtonPressEvent): + if event.button == "a": + self.start_interaction() + return EVENT_HANDLED + elif isinstance(event, UIControllerButtonReleaseEvent): + if event.button == "a": + self.end_interaction() + return EVENT_HANDLED + + return EVENT_UNHANDLED + + def add_widget(self, widget): + self._focusable_widgets.append(widget) + + @classmethod + def _walk_widgets(cls, root: UIWidget): + for child in reversed(root.children): + yield child + yield from cls._walk_widgets(child) + + def detect_focusable_widgets(self, root: UIWidget = None): + """Automatically detect focusable widgets.""" + if root is None: + root = self + + widgets = self._walk_widgets(root) + + focusable_widgets = [] + for widget in reversed(list(widgets)): + if self.is_focusable(widget): + focusable_widgets.append(widget) + + self._focusable_widgets = focusable_widgets + + def focus_up(self): + widget = self._focusable_widgets[self._focused] + if isinstance(widget, Focusable): + if widget.neighbor_up: + _index = self._focusable_widgets.index(widget.neighbor_up) + self._focused = _index + return + + self.focus_previous() + + def focus_down(self): + widget = self._focusable_widgets[self._focused] + if isinstance(widget, Focusable): + if widget.neighbor_down: + _index = self._focusable_widgets.index(widget.neighbor_down) + self._focused = _index + return + + self.focus_next() + + def focus_left(self): + widget = self._focusable_widgets[self._focused] + if isinstance(widget, Focusable): + if widget.neighbor_left: + _index = self._focusable_widgets.index(widget.neighbor_left) + self._focused = _index + return + + self.focus_previous() + + def focus_right(self): + widget = self._focusable_widgets[self._focused] + if isinstance(widget, Focusable): + if widget.neighbor_right: + _index = self._focusable_widgets.index(widget.neighbor_right) + self._focused = _index + return + + self.focus_next() + + def focus_next(self): + self._focused += 1 + if self._focused >= len(self._focusable_widgets): + self._focused = 0 + + def focus_previous(self): + self._focused -= 1 + if self._focused < 0: + self._focused = len(self._focusable_widgets) - 1 + + def start_interaction(self): + widget = self._focusable_widgets[self._focused] + + if isinstance(widget, UIInteractiveWidget): + widget.dispatch_ui_event( + UIMousePressEvent( + source=self, + x=widget.rect.center_x, + y=widget.rect.center_y, + button=MOUSE_BUTTON_LEFT, + modifiers=0, + ) + ) + else: + print("Cannot interact widget") + + def end_interaction(self): + widget = self._focusable_widgets[self._focused] + + if isinstance(widget, UIInteractiveWidget): + widget.dispatch_ui_event( + UIMouseReleaseEvent( + source=self, + x=widget.rect.center_x, + y=widget.rect.center_y, + button=MOUSE_BUTTON_LEFT, + modifiers=0, + ) + ) + + def _do_render(self, surface: Surface, force=False) -> bool: + # TODO: add a post child render hook to UIWidget + rendered = super()._do_render(surface, force) + + if rendered: + self.do_post_render(surface) + + return rendered + + def do_post_render(self, surface: Surface): + surface.limit(None) + widget = self._focusable_widgets[self._focused] + arcade.draw_rect_outline( + rect=widget.rect, + color=arcade.color.WHITE, + border_width=2, + ) + + if self._debug: + # debugging + if isinstance(widget, Focusable): + if widget.neighbor_up: + self._draw_indicator( + widget.rect.top_center, + widget.neighbor_up.rect.bottom_center, + color=arcade.color.RED, + ) + if widget.neighbor_down: + self._draw_indicator( + widget.rect.bottom_center, + widget.neighbor_down.rect.top_center, + color=arcade.color.GREEN, + ) + if widget.neighbor_left: + self._draw_indicator( + widget.rect.center_left, + widget.neighbor_left.rect.center_right, + color=arcade.color.BLUE, + ) + if widget.neighbor_right: + self._draw_indicator( + widget.rect.center_right, + widget.neighbor_right.rect.center_left, + color=arcade.color.ORANGE, + ) + + def _draw_indicator(self, start: Vec2, end: Vec2, color=arcade.color.WHITE): + arcade.draw_line(start.x, start.y, end.x, end.y, color, 2) + arcade.draw_circle_filled(end.x, end.y, 5, color, num_segments=4) + + @staticmethod + def is_focusable(widget): + return isinstance(widget, (Focusable, UIInteractiveWidget)) diff --git a/arcade/resources/assets/input_prompt/xbox/controller_xbox360.png b/arcade/resources/assets/input_prompt/xbox/controller_xbox360.png new file mode 100755 index 0000000000000000000000000000000000000000..d7b71da573d148fed476b3e2a12df46c402901e2 GIT binary patch literal 979 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDXAW%nvG~mP0JCp{r~oDs%o?2O3?WHD@bMf`ml<)BJETI zc1Albi8tS;Z`^t6z;3I)n=*S+w6S>JEOG8h3>ML8M0C-iZ&6o}5%% zp~MiPDz1ONZBMr$$ch< z&uR~zGdS=I+IDpoUMMg>@Ob*cV~mqqq#0kO%TFzy*3BUD^@`72Mvd!23*>JfDMxA(_xVu~M9AEqS8M*7_9dsp+JT2gOu{kBew5jkVhmLUL zgRWwCpsgWc1%{9F->@*cZ)-n?R|;jr_+Kr&SkmfZ=$Um zRA?#d=D2o7N0Kdb?ZFrIX^N|gbf4B=G+hd3@FP@%iJKlcWfq{`l01Hv0oDgW;6VO#hxa*YuV=HI^vFN-a^Lp3=eF;cVovF^X?tfmfBrYGA6acB3d*`J z>#hY^*eJ98xb;Nvya(gLd!fD=Zaf>lp46Qs$UN=W6K){&?AH@xAQiRNbe7_ST@3N_ zZ!eyiZQJIczd_UJ@y*tZzn|T`KBigPZIsMqV7rkuk%#Mqqu`Fc6M7^B8p^ciT%RO5 z?Fa*}`XoW-Nt$*0D?dMLPDwF;crX6O{Yw{nWF|O;y}6>BeQ(yxdWL!LxdbaLd)4>O znf9I4{7;!(TgTe~DWM4foLCu> literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/controller_xboxone.png b/arcade/resources/assets/input_prompt/xbox/controller_xboxone.png new file mode 100755 index 0000000000000000000000000000000000000000..33c8b759776cb1197163ded329cd1afb4df82e6e GIT binary patch literal 927 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDq)B>co-7)6tF-2JOA5_yxXB%YK(K|8c+UHd@XOkQ%7UintGWgMxF!)W{Cp~Yz7Tb zX2XB>1;^ZdF6175that0TgT0%^9ziw<#&Iooh^4HwLzmci_`qJHbY_2O3N*8*j7x~ zr8L8@!Ef%u-wcMO?r)fG)HS?ijJV5xVVi8h!RPG9AMhj`V0|%Nmu-W>3^#^|dZ#0I zpWkL+lezu$`LU%YdhAm(na(IYs7=p|d9<(TtZu`O(+vso@~7BD78vO1nK3N8f4yF7 zm(9MZTIWJLO-&g}G8y_W8|{l?kXZ1tVdzO^tFD^ zx~WWz_Us!yXY>b_<$5;Ms`CG~Z%J!d&d@ja#=5Oj)h%bN|NqBIh~cJ#f9oe7-$S=# zI6~AP%;(%%aqHLlb+&RBZgSUuXI3v(SofZ_d+81<_C;$&Gha;B+PZ?{#q7pk^#_?) z{;=JgoK{hGoR5h^B|(Adi}vnC>%KFemzdDtz`WypVMoz&&x$oGOB*~*4ovwzacSLx zTl2VYDd{X-b?~+>*Mm-mL%%+1H0&CVx;-VS2E#;eML-q2HA( z2Lc)APc~qlzrs>tfso{ex%2Fv^A#^x;c&q9xX}wQg%19vdlC)@D)bXr7$3$tFfhJ+ z_JZ-p+XZZPckcHU{n2H}4=a(Vw{=*zWI{OevZ)f6w=#&YY`9gtP;kSVClOMNN39ID z*C<mnBHmE2;yDLXyl*SU;%m-c`08a&Tg_inK@-?f@4 zOnZ^pi{MSy!@o`6^FR85+8@oEW*kLtG#px+OuR1tmYy^lTdK8Z|HEt=bd>4Sk4tNT PS%ksU)z4*}Q$iB}<%z7W literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/controller_xboxseries.png b/arcade/resources/assets/input_prompt/xbox/controller_xboxseries.png new file mode 100755 index 0000000000000000000000000000000000000000..8c0a1954651e740afcf05aa848cb1093584a8999 GIT binary patch literal 970 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD&Kv2kLZ0qV?%HJUWR`H`({Ul50}PnxKWD?&Dv4{p z`^+xiPvdR=_RXr{#ob>MB!dGNSNfIO#W634yRUL;1vkT^J!Khvdl?ENcAFgWW7uDI zs+_51Q)_3@2Y?_LrphD=k?}q%Fym`{ku{ET# zu+&FS;n!&3-_5g@;qqhd^EHe5>`mWR?rJkNWwNmecrvFbe%3n14zq^ihwl8AzkBiS zwl3lGi5B}2f9%3Gp+M{Fk3AI<{3#6V*Ee6_n9%FesKK1yJ5}q) z1IvH?$*wFGOKdt<^{J}gacW`{IM4Vkd+JQF|Hlhe7)wf%p4SBAaJ#5-ns9g=ZM8kY zFoUnbXMTzXL-KjPCq>UZ7zC#B+I@9%`(Ji^8IJ-7gWm4_oE{u``R^>f$iP^9?1RJBEnl1`X@otR{NPYe zPPh>(CsV*yhUV3AvJQ%jY*GwW+y|C9gm4Fza;yB(;S^PI)O6V(yf5lr#OVietP2YB z{%}p*5PhsIYT7johW1ZNUkfE$cbqAbXyDw+ax-j&8cT!L`otq&+*%w&jzrJ&<78?0 zt=6!b`Nr4Pn?f7D#5RPmPPorHA+%u<|GA(rz0=!Y7ip$_p^!T90j-n6V*(YZdhvs}KW|nt!rS z(nWvnrKvS+3=zc--^+9K9G%M>e{S_O?j>ChS?*70`MdJptp9;G=6_-S*Ov16p5*6G tz2`i(ufM3L^?$BJ10xpn;5=g;!_u@vuXx@vtp?^M22WQ%mvv4FO#qb3xG4Yt literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_a.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_a.png new file mode 100755 index 0000000000000000000000000000000000000000..2399fc263be2c40edfe062170cfdfa21370876f6 GIT binary patch literal 982 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDvQL(vI4BQEfIt{EJ z<}<0(?~<`o-hFwwJo5#;_@`>?-h8QHn4i4T<$`Df)xzmE{8WV?BZxwW1M2r z@Q5KskpDP?M_nt}kIpyN-x121< zXJYYc`h8uy_P#*{W5*O0dGVVb=jU%{n8I>5IqLkn!W--p zw%sY*tY9v!#4hZwbEN#!sm1P_Up>51zrWN~EcHb})bEyv-A5R|wcYr5hvE3d-S$lG zB5#@&O==GL-TzIHbDU{V(; zo3=&18x!Pm*B=4pucw7gRP zkGIZX(ODD(ubJ3l<|GL__D_qfYIHT9MZ(r3| zU>hbAjI6}Xk}&H8&N i36$P9Fn|;Peum_{^Xu$%m(2j?ECx?kKbLh*2~7aTJiwm- literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_a_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_a_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..8dd7cb9f77034b57f8b1695c3b178f4173163f1e GIT binary patch literal 1269 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDpk*#bgXQrNYTiBtd#Qw#SOFeRS067-Sd4VT>iPc)1!K4mZ2BK~ z_NuR|2Rln|{lU|JD||Z|m<75`V@tl4ZHzDEO_+J`>PK^?Jl4+)-)!r6K0g(kX7`4b z;r^!o$3wq-WjM`v#pEBq<4cD{hxm`)x#5ziWZAI2rlk7L-)}t5vJFq_L$9)x33qI6 z5M#EO>2yq}qnI)4-6fIdB9j;vrdUsXn|qOwtsqwCLrM@IE5{7=_i3$!F?FGTzUWfZ6@i>l-KPSUhgL92DDtj)`{=oYlCZ3azL^(e_5#%|^ z<(R2q8{T?t!4`pOuZ0@&_)e!@6-48JxQH5EpdfsfPv<$uU3b0ii6rhHD^7|J{{{m zBPPPh&dgqY)yYg30nZ zd1uvbC)ZdPT6?WIUnpy?@OWv*@^>GS7W}w+W@qfh#~0uAFHrvf%eD2!=C4=NDtDb> zn;A0c+y#{*Pd}RJr)#Z?%Y z#$2lOV9qeweV3!{i(=u0?Mps?-x(Hug=_v?(bT=i79alIm9cHrhRXPLGxJN2UY)x1 z?liW#FO6E~-~4*?Z9$A$u=w0n{14Vm{chj#MZW7pTvvAB;lKRd(ht~nFy=LYsXE5Q X2Rm5AnP+eU3myhfS3j3^P6zanPW!H8vaQ5mNGKOV0Ol7}t z;f@shfln+veoh~%m{#;3if-u86FbT9fSDnr;ULo<-5Fkat1~#6{&Upm-QM*;VGpxK z3xo5QW;TPWR#C|mHO2;}gb&FIj51OUyNtg-5M8igV5w~hA9kxPWm0mfR|BB^MDsqL>oh_ zRLe|}mRgPtry2ImR(!ap*o4L5%bt$7`dq$%4@nJmh77+|H?lRXW_(pw%99h$+@mM7 zeD((CC9R3ycXv&`7|L_$_=?G9g8%+LxA@JJvVY1N>7^-`mVBA`DSL8IWK`ek*r;{7 zKD)LV1WrryjGgte>+)w-1<~cZKJAg5@_ZKC(X@*T0=8rvSmJ8@>&){X(xRpht}olY z+^I4qIPi8!{2EW&yJAJlO@1<{RBhf>{o{R4-cIlBRz+-L=GFhruN_(P?%Kcar)Ep0 zW;dLXy?wVXmD!IW$2@WUpOcxJWbUe+{9yH){XjxZzL`x<5M%Sj4_r@#_zJ$;3vD#H z|5g1&#TBDT6Q^Wqq};0c8@wc@OFGkhLebs#E<0!RDlAui$$3!uPh`_EB|(!*U$&-r zs{j6JJhdcf(!b{{Gjs~mPE>JQhQ8$~HTJ#{<8kqXaJcc(yQVzno=DA5D&M!mHt&OC zsAWR;oAW21{MFq4PegF<_Bk%|XZ=q!L`tUi(jP8(+s@hm%0>*Hu6{1-oD!M<+d`uD literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_b_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_b_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..474d894f8b2976f5fd2eb083e9e9ea1c98086706 GIT binary patch literal 1196 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD+5n zO^X!-9HIps9lreEUuI;U<&&hZvvcOkrt2$Y7L;3UsHt>d&S2nOz-ZFIc7fqvFoXDk zA3r7TITY+?GmwlJmd9c{=lq_LT14wbdCtnyeOPB|b)l4=1q3vpu*x{r@h`eJly~ z|5r?ZvT(5npMyz#+w|R)zJiRb3(9g&m3+;+9zT_R!+M4Cf9#A~n=Kk{KkF|$DOdKt z?KHy-?{oi4#h;fn++`3wTd(|pN60{b)zSiuG)0L6YIe1iclK7c>G2-8kgwX?XT!3< zWPuCgy%5HWEDK^9;#TqaTDE#KoPO!|=!VIyF2)*B-kM-debxz#Yh+F?ii$)}8Kn1y2R)blbi9F|m6Gn}W9oWjEJUA>XL;+9!y%fk+4)|A+W zntCs03)cn4E7@%rzA3O5xX5Y$ymf>r;~IaWupz_etwjg7@_hTs_$ynphFQWw@`&0j z35Fflf_B_nIf17jW$}%dt*_(nGaR#N$V|CXz$_8T5XNc5EBNi{D~8XXQX4*`GHkAq zbf}kZIFVZC$yOuFFnfL5w7wqu6wYQjOT#HqhjN1Ngc=>SyDrH((fHH&~plEs5 z=i(gp1P|3IOP_arbhq4hVVQ^qmzT*dugB;ON>u|ebI)zOQ%X^ zpIaBIb#%EX?<235D!YqC_*Z@63Mn+dGSfc5k7yB>2j0xmkwb^fqr@}ia6 zcO){Z%ni4%&WgG9m!o7!{{Ddd_ov54|MLh=$=lx`Ja?Uk!1n}M@$`i6>slNNDt%hw zsvqW`nby2G&p9Gfi`g)HrivUdONcTjhslmDjg4E>Hgs?ptt+U$_$NlVTQff?QL54* z?f8+kiMtv4FNLXXo9H>+)M(?jm{}drQ$=FaG-0iCfQnyHcsKF>z6y zlRnSiymWgP&%Iir53WdkyKLEQuwr>%bmHfkJC=T4s&vQMVs0Dnh4bhAujIb0Ikn%_ zcWu(`nHR1}r@j1A>|`?g%8i=5>nfiPFG;Z4xb?Q<@hR*I7t@xTZ;!6Md;8z^-LcEr y3o76Koow{^&z(bwA#qnO{7byZAO+9U_H(}PN?DjPV=u70VDNPHb6Mw<&;$VL@Fx@i literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_back.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_back.png new file mode 100755 index 0000000000000000000000000000000000000000..3318c6a5adfc5f8eaaa6d57f997d43d3fda034ed GIT binary patch literal 860 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDEaktaqI1@ zmy?>TJ0Uu zH+ekgo{-FR!LxwTL~XK39m7tU2W5@S4l{EW?O`afV|dGaz}M}e9K-TI!G8W%w?@SV zfBUNNKf83j_qpYFc0MUBJr!ch;AgzySb)abR@e1&eqGov8@=J}SD85se&=UA=KQ#S z+QF6m^}5{W7TjlJeWB0rjN_uL)3dbV!pVQ8Lz+`YB*?efV*Te$UV->ttR z?RA&QgC*g9mi62jeU=Ha`W6nIj8pU`JSCcI~U~@{f;UL2`%>xWxOHYNe>#XKu zDbU#zT>WbOWac;9Zt6Dt-m1j4z?Qd=b-^X(Gs(Z09K0F~H|gs+Fs{mayFO6-4pT^6 z;Q`hQx#F4qI~H*9yKFCH*fvq1xRHnNXoXC}&j}gd7D^O0dKE~>GX7AUIn&-f=7PKI z@pl#hvSJsGB?}bD%zu16DoJqa&0~L-#$J|*zOZqA_Vsy}6g6kAE$Lhux0K<^wymxb zFV6n^&pPLL(*6lk>TGrYZ>V87!|m{dS)fjT`5RYu3&tbed(?9>+>-x$|nMK}Md@hcCDm`0GWX`wlWG0|lXkZ_kz~!L-O>7@` zWO!Z_5hzwK{36rKU^v;xqD3(wT6*Q;wYMW3>?ZzmJh7Z%5X%P@om-u+ya7a6`@$07L1{qlQS4;UtC+4DPeGQL># zXZIiB_YA&M=W}KxJ6w30&ZNP^7&(1D_X+0%t4^P1UE}N!x=t;x*M`OC)YUUAKF$qB z_Tmm|4T~rLUEEM$su0H;v5rkfG(4>#QZwP%{IzC_9&HmW-2Uj%R?*G;iyxIpeC&Vx zs91)ZYto~j2_9aX9tBPE?7H+Qa7x#yPmcnoyE-u=7ZogOb*1P{X zWlUE8cK`JM`a1JBV$gTe~DWM4fZEH8w literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_back_icon_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_back_icon_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..a9ee088896fda59792127c27a7c3ec7e4ed52c5b GIT binary patch literal 882 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDrIOlcp4mC^_V{W^WR;)=So@s1eO9T>&buIKABFCUKz1pr-@Ob0So#k&5-N%Uzu}4 z?!}Cfa0c6UmuUvC{TQnJrZzb;FwW^(8MUZrEkkkhFW$sFrVDna-PgY|JbU}xef_kT z>mAhoMjEd!|B#WzI4xUv>B@h(N56gzVmFABOU-ub*;`Y-hOxqO{`Reb=l1y}En}YZ zt>4>wL9OLQCsvEE-nYeGsdp$aC-lGH6D{q^->$05dZ6@j{aaC4#s>up7dHPpJ&TFI zAcownh`35J9YnGvD+8?HsWSF6CwZS3cv90-w%}D%`I5+s-5vzwY$jo1#PS3*$lof`^&1Z zy;L~Vo@c{xB~GRV+kUT=FU)>_J(O|Am(+%bw;4X&W_bLWfp7Bd3n8L`!<)GfgdF`*FXjMPY%{1pH;pN$V@vJTmWyg0t z|9oUy;+gX3MHBLz^&1uztXTBLb-8#`K-L1+l~aAZ-T%(CRy~!P{#$d$<_423?WZ13 zRpPwvxnF7DlsLgCmwh70+J z*PUuS`uv33D%M)*H;YpGC!YO%WMN`P=rgwCWov)kK34Yn_><*M4>E-xS>~;B;}G6s zT~rZUC|Pul}e>S|B^ z*wZ`5qduEWZSMXn!MXljJ zeAAfD%%gjMzhDoiaYGB!vM|T?tt%D?DZFcp*uA0s_+LR6)(cyC_N0WcFfzP8@_ZVD ziq;i|2Em35>FQb4j?)=@*rsh`RWRJ$#<992ufpJ>UikIU-R=9lE9{uxyi$G;mn<+* z+r{L;oSW}@bk@YryS{sV#To1D1HaNv1iP@yF__M_{8q&(Hn-^Rk;JR#f4*P-_=WEI z+zhb>d(#IDGn(H#;ZQQ!u78bx_4J1a7!I{>sAtG2`d4cpXC?Gmhp*G_d75qgiHp0; zr?F0$w^eRY$^6^rv`oLVCiFU_F}j`SzS--^aiq2{%<1k&XvT_ zGCL$^es=twf9LcWnB1P(?)|#T^1#~}sf;%+$tjm|8A z3|{BIo)T}=VtJ8t{KGdMMvm(qGu4|cWu8s3UbII3!aDUYt^uz)lGa(gW8V^a|9TSt;FLJGm9H({u)o;aV5hQduSIhhi;8>I&c2eu+J7JS2;DsS zH|B2cJ`077*LQz-6{5T%IWOqj&!~r zk$1_|lzHASy9t*frJE9cm+qcubhpI&Skwvow96ZvRI^0NgD6re{`Eo6NRJ-xl z8OzA8g?nzPLYsDUuUC00004XF*Lt006O% z3;baP0000pP)t-se6|36wg7y#0DZUsez*X8w*Y*&0DH9neYOC6w*Y*%0DZOqce?<6 zw*Y;&00000eYXJQXy*X{000nlQchFPkFPIJUvD4pf4@Hu-=6?3wDXMs00S~fL_t(| z+U=X$a@-&gM3F#3T+RP~ZJesu-p$I;4TDpuVqR^DBc{1duX_8(p%%abSO5!P0W5$8 z@Sg(2aJuY2BfB%i9|O=XU*#sQ?DTwqkW;Bd%3&seb=B7YFgQC2!0rW%-A(|&o97Tf zfVf?SiffCK045bWk`V)lxpYO2G5}VU&QMkY;3xxej03nb6mAqCm%+$m0K@G!3Dms& zV>(dN+iwum089}I(+8k0DS%7&07S9&-w9#|fG>jKc>q^L!?6Im2!|#Clr(_i09+9d z#{fuEnSk&;@E>2%H}eoC5>l4gehjpR$hV zK)?VDpHfsb1_D6e_HYgk{W4VD8Ay-Mr0yKN0t8lqj0^(+;8+PVGIRid?@A{q91j5W zbtam)Hh|_NXe}p0-2^x|32qxF!=nk{I0;@CCqvf+*eMAfU*CkuaBl*5N`m{>D?{Hf zcmYV11RodKM9-hM0F(sXV_MIyao7hCA3r)#G86&G&kMI>L9Rd*6HlmK}7U`B=l0QoiL0hgh`OjxOj1c8~*({mX$kG26aD#6=0(fkc8$JIOd zrVQK5v0*=f>p(NSC2N%BS~Aq5o6^*}eov#D(#9}2TL9EAU7K#2a`Z7#CP(_ zqervq;WT`|bww6s=x|#5gjBo40rrO2@!SB%k-I+vM-=RWqLPDH`;Yb!1-zp9J_ose zd^^zRyRYd&eX8&+8KT#8Z&aloj~%`#)pGzeep&5jwC{=A@+r5~mMK_l^6hU_#Uv)_ z_EY3)Mn)RIuw}dLM-os0kWE60eC1S2mTq-V@!HH6K%;ywRy=gQ1_q(xRqnTN1u0W5$8umBdo0$2dQ0N+{9u5x+{LI3~&07*qoM6N<$ Eg4fEZApigX literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_color_a_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_color_a_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..9699da0f5f9c258ba96df29944d9edec328a9a15 GIT binary patch literal 1269 zcmVC00004XF*Lt006O% z3;baP0000pP)t-se7FF7wg7y#0Diate7697w*Y;(0DZOqd$$06wg7j#0DZRrd$j<3 zw*Y;%00000eYXI}RPD3?000nlQchEE&#zB^-!G3}KM$WDzwZE9ct};ei49_7NIRV|&kihL3kjjn{Er*m05`x5a0A=`H^2?> zp8{Az(uK7-Nyh#)fQNMPnP}0;$M*q@o1RRYocShzm+a_o2I;>9;KwJ}M*jr>eE2#9 z5WuQ~?xyia_-i9M9yTW`+Y>;1bU&!1(u^8D5Jfit>-PN;Gc`+lQfK=B_}#?HR%Ujz zgYN;Lw+b#z_R~Q&(%#w zNCyOsf5FTBcuk0a0}zWfU3B$!v0rfwV41r?@>;-_(vj4|b z4D|rZ0n{}94?*cbqQVcw+}K26A~}G!d{lef{q_t)9f0J2Rn-P|6Ns~b76Uk$LdFt^ za{+qp(lqB3k^sJBJC*Hi(H;hHT`cfqQVs?1m?vMp%S$eyc|qa9FfWA-xSR+Vxqt6>R3deON(I0Kkhmll>FBt!f9 z&Hw-sT-1--Y6Le-0syZssX~I#j!|Y_=Yc>Q2%i7}w1YB0?!$}(rwNr|W@40H+JH)s z2$f+-Zxy9As89)7i^@=?Rd@mzAr0#7;^#mazLsh%0)&jcrX?u0sI#J3W^5hW=$qGTKFFjZJ)}XWyma7tgY7Wa~I&>-> z`=!%xgfAxS)$HJi&m-hySaCIDFQ*}&zvP{A#pU#U6oq0en2shhG7J^hbCK_7y^iE% zmX@KfNl-8;n)N!uY;#hERVkX~Ys+0nm|R#wJ~lrms>x|B5;UB@OUXyprRf%n>XIqm z&1i%(r4KMui8e0tN}RcyKI0qifFN$oCfj3tJ8?Totj}>BHz4Dsh^OxGVxale%(-3B zRr1ST=R#(&tpPdzDy(XZ;hl*&ns<3%q zu9i9Yf;1f4~rY%2Dkxk ffE(ZjxB-3vC00004XF*Lt006O% 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 R8Zad?c)I$ztaD0e0stH4WxW6Z literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_menu_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_menu_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..1848da3400b7f7ba2ee9c5ceb90721bd37a205b9 GIT binary patch literal 1086 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD#8t!5Ke3p%O0i#I++XV(G2j(CC zjB{9@o;|&X>GeOU150Wml*IPTTF1U2POrl=e?7|uF4@}DfL}@sclBLfe`RI(^6<1k z2SeVE57syBW&Wow_|<=wvw1bA0^j=gZ7Y`FTxhM(mBn!G{eA8eS7hFJ?p@8ipj`O0 z`^>u+%XYCH_)+)Jp-9(uPrczM#w)ispFDYOud-VqV;r|uchH>LpH0she|&mbuh;aP zA?LUJv7I-LIecPP=&gS=rFzaR6($~soQox&R+LBo=_+Qp&2z5rzuJK?1KtPMJ~q$u zpMCG%Lq7(IbN}ksEz)GaAtaFfhuu+6(eYV(QrfH&J}RsmcAY=4e)sjs3$>X8eys0W z<802jf#U(ofp85KCB_ZP33FE8=#b?KWSHAM*C|>#YBFQZRE|Gfn*OX8K0fz8_31~C ziJ-$#`E-UCB??CvoN5kAFuWFGk!tX$U|>)1dKT)j^FcY^BZf6MPfTWTQexl0aO{d) z4fBor;X8IQy!-xs8pDDh&koiNYRaw*KWaBsedZGS+;qfc`7MUvq|An23b%!Xn|JJ< z^@~AGE=J({!G(4Q1il~AZ}=xAe`n8z^Gq{b_G~(P?E5!vhd6G9%?vLU9;hz5GF#e# zIfH?dAy(c&m@)5s%Xa1;+5F1?-^(9=bgRilz0rF z&imHv`E0%Y##{cnHvTo790rP<0!lNq6C5^ko>6HMj`w2D{4=M+^`onfq$2-?PZE(* z+blOM@tV0wUP0Oa#FVp7Sv03FIlaj4Tkb^BB{9~9XD&-_o*DUmO8XQUw_j{`IG;H6 z|7v9K0bf{`}qu$E=@ejz`*#<)5S5Q;?~<+ zw>LEz@URAmyx6<(|NpJWPcKfGtY+J1Y`vZ-GAP>XQPJ-r9iUNY;J|r?q#LJ{qtxfI z=PJ%(N?NAIcj_XCK(RCr-$L#ivAKr>@2+D_as8#PxSC<3N$C^|K8~%DF1(8yuJkDN zGHy!~yUn3se)vR@L)Nm1OBf#idEO-4u>M-ZE7gX(HveNca5H$%7T}hjaBGT3OhXDo z$CTy`FTxad-SyZmr@Knpq5rXGDK{U3-?Vt80v!j@XW|@M>;gxAvNj!Hm|*k&fv0+d zRf(pQY_oZT{2wi!#`jF09;{8_DiS|1yT!fx)eV;#<|#WHF0sb2`ilp?3Xj>{U?zS+ zq~ZJd{&Npwfx&LaaBXKp=thB$%VM6dSQOFa%i#CXz=$#B=;IqK4@9MfxE-$MoYY}h za&w9o*M?cXFJDSXFhtz`blK2=L4aG}Iiu41r&FFBHM{rQ*6@B`axjNF$4Ib^5 zyIBtl@21S1STA~|;}1{6^eHc!YX2!{hgiM-;}^9`_4XeX{jkgXpZpJ5XTC;k+5Mb| zn*FK8@%%co!s4!Pn|)n7(M)e|me~aJjJt7doF#_Yar}S8x*|XS3O&dda-(>W7?<_u gLqFI-2?q^)U{Bb1PQjlkn-e7A>FVdQ&MBb@02xXhaR2}S literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_share_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_share_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..9f9141784512d27c516c97e0583215ccaaa11610 GIT binary patch literal 880 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD4q>EaktaqI1@ zo0A?Z@VI_#y1DWH|Lo<~X+r5uXHr+rdZ+c$^oG^62@XL={>^taU|`0A4y;<;zF}&@|E8HdP5Nm(` zWbWTdlRWodXL+gT?{<|-AWmO!nc2RrHMX+17^a20%$z2Y9Jg`z76zN<^E0pMq|b}a zSk2Dy!ffAE0q-~g)xX*CtIUHN8yE~aYpzu-n15B*)+_Cf&5>(C>*1+)uCP6u$cG>f(C%*7M}s%~uPJ9IvcpnU@im zvBhyBPurE_6MoFjOWc=lB%jgPb)xLMZ6!l7kIIkeBQs{1#h!9Ip5uOVzLx0gTe~DWM4f D<^PWA literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_start.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_start.png new file mode 100755 index 0000000000000000000000000000000000000000..907a954a2a1b8f7635694a65b4b5bc433e1437c2 GIT binary patch literal 879 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD4!>EaktaqI1@ z>(g2kc-q#AXwLp#e`~9blCqcJoXyWw%##m3lvX_HKjYc+)7KOjn6RJ&`xz&ch;%z1 zxaOuj)s<02eL_ylX(xtB&dZJ}urOX(qImnul6g!Y*zd0A_#@aL^5I6y8TJKITr-yK zWMCHAq{yGpl4SObp+F!)4YR}(bWCufFEhTAG8_hgw0CPxZ3+`Sqi&YLhPQjj6zX}ebm04xQ?F>)N^C~Zcw}{H8CwGhFd5Tqf z!zOH4uC_gOl0u>C%#*Lpt_cjCz&!tc+KqFC)k|FJ@x2nk>ISA)2@6! z;IMJlo=g8@ckWBrTJU4OwAlUon`SgTe~DWM4f DqUDO< literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_start_icon.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_start_icon.png new file mode 100755 index 0000000000000000000000000000000000000000..ac6c97fa6ec6f63e8d5a991d889f890343f92acb GIT binary patch literal 666 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDpom-{{GUA7=s@h6eWVDm-0V%j)}R zN5<)sA`?z26!uK&VOTlA$f89vfq(9mi-+sP0{oWPb37GMFtw7^eaX0Uvg4OEtRY5S ztC(|A#AdSvoG|E;2{>u8N+e;<|E&*L70R+5`h^bU&HJx-gSElh^2l8N6;Fhwa(}2` z(3d~-D7x|4*Nltz8p=X`)Yw;Z&tqVEeY`GgYQ}nz2!?yoAF6k{T(E5LF>3IbAlabE z>cP^?_#&yn;llF+#f#nU07(cM4a&c%eL<=lySjJqyn#&b2g<)C3OePQR1TGoI zJ+Uq73URz1;mq5*8LvK5joHc&_wVXjfzZ`XOLUH2T_f_^&h6^EWaLa=jPgpe0q;pUNu;B@Wr=fYCW@Weu(<0XR`U>*K&62 p{$p{%dwcg7*Gv3gfEHmOt{vk{?sG-@%sxLrJWp3Ymvv4FO#uCLB`4x;TbZ+DxmERt*P6u7Cz6kp&EJ z=7;+Xd*(j!f8%sVZq}OjTpzq|tyb3ECwxG^I$425P{8NjY~|Snq8nl|{<%fyG1lDC zcmML1;rFlS?wiltS1f&b-gtGV{{A!DL^sIkD~9jhx7V_6?>5F|$*w!6i9GJxYQ2?V zUbp_PYdX*81!t^eU-QDGPC9#%&$CzF>uqXa+@U1@r_9hvvh7V4!@?cKH@4+9 z1u!o7{>tlMz%(Pi4Ys%Ke-*bfK4sr_ec|FWD=s@K*fGznJ3g=Vyt{fs!FN8VbNkN< zGgWJDignNl_h4Y2@I1bZZCh~XY(~!t)h(B03h#>>F===oSjgb>U}vRok7=F(gRsEm z^+$RRL@_q7Op|0z`lWS+L4nbDQ^`KwV{5q@I3E2x_&~m4VxPfs1_y(=`Yu-Ox1t(! zHaw5@e$JV;j;*8V=eH{wOz-k#kAlKB zy~8ZJhaa973Vb!SLn^rN)wHhLX39HjD~yYDZyR3tH|^lt9X1iA3XZqh`Bk2JOw(fU z`28mPrt`{n#(>{XlbJ22OfxjlV946PhVezjzaK`zyAsx9?Jsd?Vvwu){FlwRS$@JF zdDk~JFTWT5iMh@B$LibD>|5Wn{ujDFy!gA3)hgk^#Cti{zo_Ll3G?igmB`XtS>%53 z@v-w)V=kZVPmX(W_5YnY)AY2~b2;rSNEVj%{7|@e%fBNvu1VM5E~~VZdn#9FG|{GV z-}}|Kk0nYr_N%Q)nE1N-;C_+aQru$uz0DR!KI{(OK5L`<$!A+16)ep7de1jPEW1BD zb@$uwr^^+eWC;iCnme15Q)7F>(bsJ|1$K+Be{Xqo8Ao_wv5%Oq>@Rr{^n?gXlMlGx Y$n5?2#XhG7nD7}qUHx3vIVCg!06}S{n*aa+ literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_start_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_start_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..ae48df9cd77928037e4095776aa4cca2da6b1003 GIT binary patch literal 1077 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD)x!e}4Xc{`U-LRIK(hFfdQ_ba4!+xb=3{ z%}t9H1RM&J+5i4uFUxys(E?o~Q`6A9`sQjXaxbU8^L|#Z!@?-QfCc@NX0YR{n*7wU zuDflbIqL&2z4fkI_c$6>FAY-Q=x1oVwKPh{G@kKF`|bY%SJWBSO+UY=()NIa{k~;c zQBO0yx0~8ty=eY6g8M?dyyBa+^~F;A?p|l)Ik-5&w;yTX2Rr!;^WxR2{eP zHj-J8U-^Hk!<`w7I|C2gSoPwDuGVUnpgxB!u{Rf=`q&|2z92iY;^iZ;6ozjKc3W8< z9Q8fRu!DC(Znpn)aUGU~k_~Hg8T4C?_npIVpv)|jC<-Z)bv+V#xlh)3Fw zZ<@XCdzW!eyFTcG&dJ?t_KB!QNAZciDifV6n!>YTXZ0?lPcC0M941fLXxr(hlm6S} zg_qyt66@FZ=4Vtq+h9~B_KRbIaQ5DJQ9o|R1|Qp7yk~x0-ABE@914>(w(Nao&hYSk z-O+A_;3UCRhUGgY)NSwDFiZ(8_?4<~V8fAne_1A6JK)Q(XLTeWn?;bHa6>>ynHqb6 zM~Uij8GDvk@P7(Q3b&umb!!jB61t&FpkKgSr7gt_* zmMJ2^%8y&9@MGS?sfBxl+}A3k&7XB?>Uyq8wr5XoaTop6xXHcEK5pm!<5zE8dwE`4 z-=163%U-^Fb33m`;I7vV+gK$Yr2Y)d`nZ1sqY%r1>EF~RFZ>a(q@pXc+~V6l-Nxm~ zby|$GwrW^xaWvG)J%9YuvgjG_XP5Y%b+~KGzw>gWbkpL;$M0=k9e_vjoKmL9F{`m~)GBe#77?@Uhx;TbZ+0&N9@YdSPR6FhKmYu1uiJ8~dx~sd@7+A@Kf9&uR8`apq~^_!Nn{jDfTDdG3uGFr zeuvII6lljYVP*KfmA5p1Gxybf>bCm*kkjK!(U;nFxXPK=0!{#&l^xKQD?El?=w)uhG zKZg%o0ykGI;+wEqbU_;Lf*HO4^)$E~k`HGUF>K77C^F&Zj!gm|Hn7>GFeH6e;ymz) zQ^JPx;A++dz7NGFi1@M^s7qWB%@q|W5htheR-~V%8!!z!T|Mm-* zYfG3eTz&h){~$}koxY1_9aR-pO;2%aSQ9rzit)?oB58(AI_mxmpAw5Im?rpdPXEg6 zAiw;9*i5d5+IjBw4BH$V?=tf;+*x$+U1OT(we)Q-br@cl-7Wsk_~wIO<~PQan$1!b zvi$z7`fqMLlWREB zv033-^?{R%)I7|)o6fSENT)u`O5C!$<)HCTjzbf6+uWJA?qB~>ueaes|L5r4o3Ffe pf0|3S$mUneuhmEB9AJVWY38Df+qv}TMFCSZgQu&X%Q~loCII76e~AD9 literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_view_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_view_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..33e28fbe638eec9f97e3d50959b7c3a67cdfd14f GIT binary patch literal 1168 zcmb7E`#0Nn82)^H;}$~5=(LKWGWSc12qn>!q;bESTg;ugE4Buy9kiQ!doVgtp{zBj zyVfPlp{}P5+ZfxzsrH1r#8@yI%g)(9u=kwzdEV!o=lSt@?|Ha6qmbv3000z)O!5*l zsCIie2W; z3(M0qb>JxOGC^y?-y8toEeeU~lfArM_r8{;C11#D)WY6A^B=h4o%lmRIr_!^mc1ao zYFcg`L6=)GwdRA14R8iS;WdOGaX_vb^Z6Ra@y|qTs108j)L78E_zGUa@ye~7Pbh%J zG^Z-+be?g7}N$lCYumw89*ConGB!&Y=31dZA; zcrO*BpC-LKDrAF{$`73O#y7A!M7}CIN7UCMt`)^&tbA+83N&jbB{XaI+wfH=_SC=4 zs-X_lNR!o9=Tt1}K>#wKS1)n2(ouP&i5@dL;~}4U7<*mnuIyOI%>sef!8lpc?0eB$ zufc~uqm&3OFTMdTe<*o@OFcAgMvklmig5XQwLEQZ!grDz8iBSVtI^7wDiG*OCu2lF z+HHXXZI`*92%Qd++K%Tg^u;Ju9Gfr)O^G_>P~Q-y*j!g!$nuRE^0+ZRS$5VS?( z_Xd!32jR@;2{YBqV^#K}IrF5ML(96HYoqCOhX`BbQuy$ZfEurqL!ScbKcPb9Rq2-L zasEBo-6!UJ1}7KFWohU3*wmYJvBLrx5tvQQq`YB^2eWElCtxpW*=guBFYc}5{8vWt z)l7$uWp`d=)e#y^3zU@6Fj6sV;3BM)iQ||Z(s_>VXWTR;Dsd9-#yM3LUX-%)Zgv@m zC$%vaDiJosY0HHu&+43kIaE_H)_k_lTveybMqg3iytoN#*DNw?>}{ARH;`=t*tn&# zG^??O(2x2^duoC6$Di)@%WlP47Z*$o31ba6PD>N)JPmC!Eh2}0@hr-c-_WEkeXbLg zUEg1;zb!OdWhssn|4L6C2%KLQCf_>w(R6il{lnAwopJ4l3s2hP`DbH||FxAM%?Ej4 Xa(mF)hm?yG{{%p};YMnAq~-quO_>)B literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_x.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_x.png new file mode 100755 index 0000000000000000000000000000000000000000..b04ef414e98dbc15274a7525c3242778643c1e39 GIT binary patch literal 1037 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD0p`xmLntP|TKVCd=^Q zX(fk(LxARHos|ll3$CBp7TV3&GGnz4nv-v5_eXHXhHU!j8OLga}O*#`e0hLhJBST=sG7i!^Zu;spWj(tPq6&>*d>4~3x z8hoV^9y8w1$(zQkAj#;W;&mR7>yO zb=oiVZ*$!ig#(wfa~1}y*vI8pq2wXa;I{3eXm{O{t4}+hH?_;p`(IGOcb-@M9^Vv; ze*b504~ix@UcLHoi`8PQJVpkcRk{2A=5nv-+r)5Svw-KbD8s(Udn(Heb}|K|e*LuV zdwgrzA|{5FF5j+qT$Pc1?#Xc6>*c#Q(@O*kRTvap`}POET0BEqL4OZ}!div!`U$f< z8C-hr%bP4{KI6yaaCbpTa0IW)<1itHD?7v+#T{gISqlD6dEw~A^vma}=oKJf>$x-Qgpd8q9PHaqXtHIY-1A^dLeVn%`bu@TprS?qQIZekuFa42q_IPpcjsvTA*R!{0 z=LOijYt&->GyltiThS)xB~7OV-FsgmV4Tmcb*p4P_t{ow{)yce&F-?lUM9C)^4*kQ zb_{a)Vef6P-Iu)kN$+mf;hh@-KE2ky*6SIu^m;t=`*R-N%o*>t&aFDlX2WnI?~v3F z^W|}$UR}E$6ZiTbpF_<3?pV27w;0(Lo?abce@JlmdVk)HH}>6^mw22hr@YW@<_Sl& z>*cj6Q&uY~uVq>sGlxy&uK(IO>OHo1=9^yFafQbu>GReP9_qXQboQ0hT~oUkpl$Q$ zjKu5Mgq6FDm(7lBKFj^2chAGDXBp))I%eEadp5zke#hM%5fijDtq#}4+^yJuz0AJy r$iC>etL04p9Zmw}5(5Tsma%8h6FaAHeZ&5<3_#%N>gTe~DWM4f%?;yp literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_x_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_x_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..e5dcc05019c60eea0bf3da2aa1ce16deec8a6a5a GIT binary patch literal 1336 zcmb7^|3A|S9LGPKO=HGzOevNxbs?4=^QCXoFn7Mpx6MW7YeGlK6@B5uxsBtDbc~hM z)S;q$$?|m)VVOwQ$wZ@W9rCSwne2A`2lskB-tXu8{dm0JkJoRnEQ+TqN_Cqm001aA zGRa3l@V`PRDf;q6{+a@?D53`u03NWA8=-K;j;FZ$Im`c#6i z2XNtXKdOyxXn~sgbt+3pU}N$hG#f~7*E}>p>FY_p^Il8&A zziDt%;Af&+M$q!P8b*@wQz#-+;}w|cm?b7W!NAn{i*wx)VoY19SwFLHG+F-sG4PzN zz35iB+|X##8?@8(=;1BU)ms^F%T3m@$HluPEXs^=TCz$Ahh{zU<=Mj)Gf@2msPmwF z*kxSW=Y{D#gq3RJ_IcaV1Oh5u_q}bfWsIO59jU)vZ=dirbxL`iF+?kYp({|OU1U{U zqZ1Jt5M!lr+!cwd+wlW5M-6n(DF^UiKCFy%O4pc>wrAy{CRW=xs=rAoB5bOw<{HF$ zU#%8<9PpinE-Ux+=M@}Tf#bGp#a*s21FD+q%#A*Jt7-FVNs$A&J+&FJGJv zhJ;8K^pab0F?>m+V}R@Wn)Zb@Q6R@O<);7LJklxa``u9GVGe?-u4lP_QvL2irID^U z_=(dui-=Og@FIq@lEI@QLd4D&!_*$qdVbi2NpIRo1P1_=h{1O}dl z{~`+deO`aETklM==T!)_d?jqnrCQyvbj{a8*EU5kvF!Tt@0`Oy4uvzKTP!6Q9=uEC zP;i*nr?n~h)GUU&wMo%?r5yCOp7grL$Pt~pMK^=t&DO);>KInVy6t6HS{C(^`Ng;M zLKeF}AAi?q{QbN@4C9YgDf@U8WTP0|vlvfg{i_#x!qyNWs(XrY#VRRJ#-Pw`Dl9j) zFq%m+gqA9EHawF)u!r-U7V84fhnfy6)Wi}Rl9&Sm8V(%2`sa)6O(XHW%p49mbHAUt zxW1u2)?SFC;A?u$PuE3N{uV#G9von>DeC!sdP#gr<;|1p9=vS+Zf{}hUdB834NF|% zy!krT&Gn38TXtEMt~QQ3uFJr%(DUu%eYujpi&+?4cI^5!+3;N8yiOKD27xD2?%$hd zs5t!vD?@bWoc;O#Kjfuu>+#TGxX+#QegEF>Q#0nWDntg&RErc+7E8!G_a;DSZY0AB zsr$2~cb;HMxV&KR)`>BZ4cD}!*FN6LAa~n#$HXg46&gh!jiPuT=qaS`>A%Qq!LH&c zzm(&HAfHO;7Bz>rKhJz;?bycf@i((bbi<;Febo#XsVJ8B3qC%oOco%sNp$o9ir3^ErVq${v$XeICBV<_<{ z7tVcEIQyYz<|amu9~-Qqe)KnG?mW50%0orT@tfM^3G9}_1^vwI_vg(T-dRNL&~sLSLdb3a{Exv=h1nS zI-?RhH#=P0ePC&p$covnhn}(S>NP6d8gOf_>}S6`KaHa4-M0_N#ZA$2zuEkwF!$5H tCz|)}c1WCDzRbP%{|0D6W;6h%YJOI=i&-ms-_|k!fv2mV%Q~loCIC5QwLbs= literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_y_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_y_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..affbcfa3c02005b8a81b942da35ce8c53511b828 GIT binary patch literal 1253 zcmb7EYf#d86#el~NioO`dT5BY>8g=YQtFn0De2@ZGb*!EoU+oCDcyo>`im*Bw)vna zQy0cI&2$zjMSK+GBS{T>lp(2TCFW}?Q%iGn*>C%G@65U9o|${*&i!%(VIh00E!SEC z0IUN8{K8EP{R4P{FnYunM|g2TorfLG+~p& zL-r?yW=joMj%)3ADc!}GPhgw9^F?-=q@Wq_t}@|~PTw8tM^atd z%@#G4Rg-Re1EF$38@!nUTIT9bza$9P_rh|F?uubTwUYoKqIuhK$7W_fA7_}eDkRB^ zLh$yPC)7W3;T(xDbup6!Iw&JcHCf*FgILLfIeVlFjoY-UH`416(}4{2r)JJ8E-BX- zcYw4jZt?9O9e@!=S}vf|aLmdW>nX<%l7XET#q><%Al6#vxT1PhT@oMfX$f(5K7U6WOWBn##$;R`nF;j= z*!w8<;jYt3S+{C5_g622tHy;nzkl74QoXqFP-o#d&Q+7$%K|0~Nlhzf(mhn%LtST; zEhF~NJIX3vWzcj{6o*zlNmJdnfxSLB5aeFJnkq)<_%)xg@&;QKUq4s`c`qeq#M)5z zEKpo6Y{^y!h8{nGu`H>4;!U0sT(lt$BQMa3pG$|FZ^3mKxG%gHKj;{6YU&#a$|C04 zsC&RuVs675BPg}&nBvU>x3%0?F!e0RLs7#UY`_DXMK8!9if;M@Kan@CKtr5$kFPa( zT2XoXg#iIZJ;IY2AU_#@M3l(j4L5^nS=80trIAo>;cZFTVKS9|Cex|38LW9Ry2-zm z1Ze`aYvz245$Zy?S!A&bQtjGlCzelwgw)`igWax3Ec-<5dc)apmsy5Kp4+`PdKFx+ za+N~33kM43a14%lf>v>stQ^(};P{=DUdZ&Psj$M@XSV6f3@>$6ZIShrU$-iBZ{1b9 z6=T)gTFTo_HLHCv31?^dr(LU?~xfay(mOoa-U6sR!u5; z(w zk)dx>DJ$v6NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh0azWsXn`ThGD4zHCC0g7Jsba4#HxcBy^Azzb$L|bB7 z--9=*DJ5Unidga()D_ko6ENq9^7Vh2wqU<%%|(j`zu(qg+nzc3bh|fDa~S`e?OW^D z-jh#Fc&z?9bivnyUVz0NsoW_CeJ-mWV8Ps)7t4_ zQVN$F4l=Z|Suih=x*&O=8>}qC{6fZadA1cM5AqsfnDg$R-#sn;`?IzpD;@j#yM_KN zAfqNUsLguZ>L7adqmx4avmfF9j}zC-{jNS%G}T0G|-o z=Z0?2joe-s02!{&jNG0XIz2Z6F@Rj45}?#P`LIwRi>oBaFZh0azWsXn`ThGD4zHCC z0g7Jsba4#HxcBy^Azzb$L|bB7--9=*DJ5Unidga()D_ko6ENq9^7Vh2wqU<%%|(j` zzu(qg+nzc3bh|fDa~S`e?OW^D-jh#Fc&z?9bivnyUVz0NsoW_CeJ-mWV8Ps)7t4_QVN$F4l=Z|Suih=x*&O=8>}qC{6fZadA1cM5Aqsf znDg$R-#sn;`?IzpD;@j#yM_KNAfqNUsLguZ>L7adqmx4avmfF9j}zC-{jrxWP!PsM&_GSk!QwwJ2PXqL0wqCy!Sd_v_s`G2U*B)PUVeW6 zeuna`_uYVUKRsO>Ln>~)y>*kf*+9TGaC7ow)pz@Eng~cJe`4u;mz&V{$6R@LQ86D- zEeL$5Zph`byw3krW|=R4#H++^sR^qBFEQ1aehzZj!WhUkBlmy~>y literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_down_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_down_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..f31d0a5c8a0ffcde2b4c4e5888fb654d15bc5c5c GIT binary patch literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1F{QUL#{rl}1=9NxXWME)q@pN$v$+-9Sh9TD>1Ce7N z&)7DZZ*YI{?$MhC?i<84O3E(;29&IJ+wx>e*{006Pc{63OKR>1@64Q>ShS9&z%Hu-GY*X*Sr$MnbrzvI33Vx)M1fQy$~DuZMkpx73MR7Kn)BG ze{2m-Y+T&Xef-vvqBD$jYkZB4>s&Z~OQrY>vo0f-Yy#(mmkn(Uhh;v0PR_me|I(lR zEMRRloDrVy<&zxVhExkIxWZV@^u+3c)(LCtS>HY8&AfSn`>DzF`}=cM`*oIm+Y~MZ z)*`_lpkA!Qaw?^-)nVS5kL&Gv+c$2z)&JRX&0ZOA#+N`f22u|=*&h}u2^S|AfwXwK L`njxgN@xNAC~~Ys literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_horizontal.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_horizontal.png new file mode 100755 index 0000000000000000000000000000000000000000..09dba4e577b31c522b8e8e0bb4bd545bb24b77df GIT binary patch literal 435 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O`1!2l#}z z{s)8SMsCjy-GIy&1|X5=hG5dj^_dY!5D1<7wtE1_nk3PZ!6Kid%1QdGa+I2(Sh|lT6<5zW$m= zBy(dstWYpOk=uH|ME`2`OF3-neUblVka_1H1i3BNnc=7 zRoJ4o;5t*4a6lI0QcjL22PEAL2R<|UaTlm1TxALqHppiD{Q3Il&wqa?E%3L#xTTrR zU`xzNWYq<-58nQaj$h- Wd<%a5J@D}#NXXOG&t;ucLK6T=O|5zW literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_horizontal_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_horizontal_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..5f7094ace2626fc293f06f49b32edd4fd2912008 GIT binary patch literal 377 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1_eEIzK_Wk=A_S;*k14Z9@x;Tbp+AP{xp#v^f#g2`WD>gGXuI0a?FyU7NYvU1iErE=_Gk3S;g~@cRVc`%^aCq>% z(XUUHWr_VYx6_3@8OLv}ufyXJPbP-X?Q!chik22QpRMg<84uJOph}+#+?1=pb%XAXYKMr q`)gu#ci$h|H8uI)MxezGGu6F_eYU2lEp!9!F7srr_TW@bg3pFbUum%c0mKJFJyWW&rK-PK2r6s$< z3+moq33+`q6sQ&i8tyZnnf%#K?bAKq&2|g=+rQpwQ03Cy#UOmNN9({ThSgjzQXH0Z zb*M8zl!M6w@(j1qjd>I!&*bG>t22b&(60-xXER{4bx5+#XDGeElC%9Y+lNc65rQ8s zu$Cw(ykJs7(#`OWU!sIDj-_W;gDcCHT@63~eg0D+KmFdZm%_exUogyh=*5Ply5T*e zUGjf1RvpXF>s1oY-PrHSIDLOv_x`J8SJ~DatKCuW&CJCPRlh^#1KWzErMbRm+rvRZ Mp00i_>zopr04OA?5&!@I literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_left_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_left_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..c813a64ae1857efb32621658edd14fe7d44c0323 GIT binary patch literal 389 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1_eEIeH_Wk=A+z*}i0E+(fba4#HxcBykAzzb$$gz*- zWDY79FyD-w)7!~ja6$S6C&%i*!);d=R4#Av%!t4JWu+m@{`}V$?*GbPwTgc^&;WzV z2RpQ0Y)vv0XErt}o601Q52+wK|-*g zh;gZojz8NIqX!2YeAw>nn_oWfxxfNh{VAV~zopr09P=e AwEzGB literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_none.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_none.png new file mode 100755 index 0000000000000000000000000000000000000000..d36e045f234a572bb4bf39f88236b6f2a3a2d5f0 GIT binary patch literal 398 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1F{QUL%?fdf?rYK3NF)%PPdAc};WZZjuV=wO^1A(^0 z>ynMUCpcy@TwH#Vo|KEEt0A!)8v1+)+J!aGw{p5kNkGCXrebrpQo#z%Q~lo FCIEA}rhfnc literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_right.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_right.png new file mode 100755 index 0000000000000000000000000000000000000000..0f874acfe7541cf90fcd3c6887da2c9c448a4791 GIT binary patch literal 427 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O`1!2l#}z zJ~wiEZs_*H!1cKyknQ@+$nBY-(|<4kiU7F~HW~@k4b%t}%;s`P26BW;g8YK(@8{dk zUoU^Zet$ohvA>_eYU2lEp!5$<7srr_TW@bg3N;(>umo<}AiZJZyZUP}3=@J=x>T;# zHhs6&3VnSv6sQ&i8tyaeJeFCUEArSzugW34y)?_gX!#OWmSXOp15cRMG#7kjTq}~m z$_P;nCLi!K?Ebxd&7R2n*S|00xo~smr@By1g_(R4*yRO$I8X448)P#2i(klMG#5{Z za$rN!dEh(qBC!j*86%>XraG=uRU|C;~ z!@&X{AW;^#q~Ub@?z?Yo)@B`eST^tQD)t%+sL}&Z8^oVHU7Gtct~LxL^YlsqFv& literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_right_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_right_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..0c60966d78e3c8c3e6582a1d84479de95c8844f4 GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1_eEIeH_Wk=A+z*}i0E+(hba4#HxcBzPM&3gP0pOnvu72FTm_tCp zpd!Iq`%H4zNeMQ$W3z<0WO|IH1tNAk@G`CB&Jgg3S-{lz?8KhC)6RA=H%?_>WMbj? zW0UaohR1;~$D@80$uRBO7u@2$J?3J4zSE%5Kdt7Y2f@Y zpUGwa)R_4bPd|HX&wRt%Zlhqj-tWH*cIqigsrBj(8gJh0m+;!bsldR+e2ACf)5}s0 zr-mK#R&*8jDl^77m8zKYC_Fp%IQ;p1{XO6FauuFguqo)gKd<-x@TuP`RC^dKq)zYN z%9hvLc)YaX82{m0a#4X>LaZ8kKHa|>rG4f)!>rHB8@Dp@vMjjwU*3^7zu)c+!;wE; z4D%Y83oO_e=JF=kyED$&&8l!IzFwDM-bIFhLK%jyPaiOF^|36l_YgVIm&{lY&ZM)L zQB2n1;6;WjY7O65W-xY4X6O?6P$R;6AUWOA;X(J>EQ_5fe9V8DwwxARrSejj@r5|U zNvG4F+85X{?YJcWP4p@|M?xlnS-snZK;Sf~53@6$hZU7*Bz z!mg=x9=mKS*gu@P?_D)_#?1H!tPD>iBf2)+?@66FpEJ*QIQsFALH@r`os7cdtGBlv>R8o%vSd#$>y0BXZv;j@j#61=`m;{; zrt`wDt6wX62L5u7jJrGIWo+owr!L7sueIVPC9l1DZ$`ztppZ+m3a_u8bgd`fZ};`f zzn?yvl7IfZ-&UD(uiskg2k5-fJCfSqaB*VBswIrOR4#rA*`<`}bku6DX2y zBbHSkuys=Hn0o22+{&`)-B;(T&%9F9B&{=RHLpU3p3b$8469!qYq=hy%P{4T_g%@I z7a2mb*tOYEd89GcWGwSQL*&awb#R6 zehFPzu(Ic@o%gcqGgnW&_HM_=yXtzgFFjdjzP-J9myF%#h5KJ^y7{|2JkE7SRQGM) j{#pMM4MEWW%_{me`A2&8-b<1P=0yfiS3j3^P6nS2{ycsD^Yn%1 zMs7f}ORt^=IZB`;$S*kmeE)j;`~LCz^8D}X_s?f=RQT<|z`)$*>EaktaqI2u*OO)& z@U;3ewR9JneRnXZ`@j9pmd%;Fk4{om-gR4Y{(Zh*$Bsoz^1t%0d#R8ElLrH*0;5U; z%ZK?)COa=J`>nb-^xhxi2Cv!IDz8YzuH~OF`;elR&ptJWwHIH=^X!V_TyW27(YCK# z3|~IjuqZif&Xo0jyKEwh2KVu>z%E9Uw0}Q7mc0A@(z&+YuJ^OyvWbia zpDWf(W4KYd?7Ly*mk&I5blES=JAUh=_G`&i)0rgh|1SN^e5WS)tPw+gP5DRWm>8DE zr1k%Ug%$q4h_zu5n9tcDoWQVjGGjrijKftg#x0>d4$Sp`cQUNYRbsGGXMbR2*HAWz z;R8D-(~3Dd4XbA`%rtR0#(cp~iXn>S=v~H(3^K(Nx)~>y*cQTnui{A7;U_P+@fW-lZ!yWl7QVyN@ zjgMPibmX^8Vrkgd#BTC$@`D34yrTK6$4-k}NPV*Qpp1h1vj4d=nxyVcNIk*H@IcPu z4(G#qr91pzY8Om!c@q3reD(R#WOs%Gul7g$T_=5*A@oDD^kViI(=}FzZJ4}5Cq!A} zh3uY_Om`M+FJRliJJ+JHYHsjt1qbmHRY|P5&5{lq^&A9Q3SKkqV41)mbl^Mtk5Bp! zr!(&pozkooCv(5BiI>ibmW7pW|*U-I;uj_|*n z$V|(C84=Y={wLq2j#uPh>Fg_R;wl`r_lS{?sR&H!I3| zKL0qleS<)ck6n}C`u`DOT1`_|cWf^bWeQvO+}u#Pa;1&ks-A@9%0D@6XFq(u>toJO zJ-xPthW&;nm;P)`@m4qAw`S?*<2R2OZ=Ixjjwd?*Uf|(!o^s>UH|9utRm|HtEAwq$ zNSph4k;_lcnQy;t&Adcw?aixijvu}Gdv#Fx{CgeiE~koqUGas#8x#}JjI@W@DIsGW UvoLEXFt;*zy85}Sb4q9e04;bNga7~l literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_down.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_down.png new file mode 100755 index 0000000000000000000000000000000000000000..3dddd32954019b7f1ba68dcf96d0c549afdb309f GIT binary patch literal 1112 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O`1g2Ka=y z{wDOC{oLL6#9yq8-)seg=mMsY2Ce``tpoL3 z2EB7XKTNt;`uNLv2DazZXKXmS`{6%^#oH$uygpvdbf)jizxIP?%b8{<8u31}X83UT zw7?98;N310b>A-GOvn+LZJNa}*}rC=>EqPH{fje&JL)bm&X|2WfA{UgcV!{Q*$iJg zU%m}u-hJdiowdWG_M;Qzq?EE(r7+list?avx9C3GgfH?Fm(ERQWbC;9|MZ53FE7?w zGCYdsa1ah)Puit3>*WUVikD&p> z4^Q6vGXMG*r8-MyCB?KKQ*C(4 z7jN`UX@=@}hS{tg-&s4r(KMg`!-v@)9x}-F-rL6N77^29cYLm%!|}R4-fvks#@$QR z?DbV{b8e|Cv^m1*wZHZ0!52Hs9Yv-ocb?p}?@hg7?*C2eo_2ChoHe)a`JV5eKW@=A zRi1qLa`w6ZuQRiYyp}(2FTeHwcVcywAGe7A6RkP(MOeCeTs8+WtZs9e9Q?9x$N$AK z%e5!G06zlN5q2wfkmrFd)0+*yVp|e|g5~hjh zMAbHgg%~b0y)MMy^YCbvo163jkJDvq^&GU$#T|D))(Q*Yt#;}D`U%&SA5lIm8ykr;nB=Jf`#NU?A!yD}4ch|=~jg6^k zxEpcl!imcfH+1I){okcKcmI90$YAZ=Mvu0cOTOnYFOYhmm&v#5W4d;&Dy?oum zz2uvgNzkl RMSwY$!PC{xWt~$(69B6=5)l9Z literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_horizontal.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_horizontal.png new file mode 100755 index 0000000000000000000000000000000000000000..f19d5c08b7accf74d8fd58b2db67fbe55a00cce7 GIT binary patch literal 1118 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~q1o(uw z{wDhq~@xH}1TS$atGt@@46|&odUi@{ax3J?-zO&oAr(|2%#E^Yn%1 zMs7f}XO?b11=J=`666ibm`DT|LT@*U`+<|;0yJ8RSoGbsjySo}VI-uykNWhg& zv%BtwRx7*f*-mWDJQx_Y`14!Ft(uJ*OIL1ZF`1R~ceW{W7<$$lK*Jc z3=6Gy*O+DOd92E>^6lBx#cuH6|L&Mt)->4zH|-RhZ#y(NY&`xyaKo#2w{{mWT(T2( z;MQm} z3ktjk*c=PP9Sj&EIlDe`R!p;I+cCL5?GGcbY|dGUgk}HNePp{J%V5q^z+5M;QOACu zfsyS&d1I13!ySA}{@>1+&QMmP+_vx=!-DJwSN`YJF@8|^qW-^hL5D-i zE;&8%*Baan_NSf}?eKF+SoA^PD}Uo5#y!*J{`YB`2b zkUqOj@}q8@2TezhzIgsajeAFBec$}slRU)?5~C--7oYT(&#S+vL0MVjugJ9X?2-o( z(^{XNIF>SRy?gwDk5=a&N((1tn|-y5-dq>8-E^_}^5?1tD!)FPa&O*!b}559*YnT6 zWbV4`!FX))0mh?;1kd^~rgbXMS{l4dj{VD@;$02NyMA{t9@~A!@UGq6XA6wJ+G(EM zZn^60U;fqB6YO=}*X4>l<||QM@GvGK!n%OAVVI+?5Qy)X-I(JN+b+(YL*d zrW`d6kstE?ichY-ZS&;Qu1U-xiPyhJaV>BbmgwO*(6YQhg0mrV@{vWh&fZFM&W5F4 z+;Q1MsQhkA;C1N-B2)gEzw>_kdr8sMhbv>=7Bww*H;P`k$Edh0T6ObIap&*XcQ5t2 zdAXnQ#&t!`-nyS#?`h7x^UZia`)~EF&(Gd`nB85U}*J>F> literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_left.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_left.png new file mode 100755 index 0000000000000000000000000000000000000000..a031aa25cb4145db6259810ad9b923f363b7fa05 GIT binary patch literal 1125 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~q1o(uw z{wDuN!wh)OEj($atGt@@46|SKhIoXDs^IJ?-zO&wrjie_JybUFsyxd|YU}VVW14K*RstN!hFq?HXSEX~@atmt=9<^I!aM+i~~u zpA0AdI5Es>U@BO^%%I1Az%Pnn@>xcS57*G-#G%_*N9aAZNn(ft{0i!vb!G zFphvs<`5$W{#y(;)FfW=o?vsxYZBbiaP^)cgOv3L#wx2DR_S+QuCz=3WI7A$5~a<~ujZlOPm z;d<}eNer*EY+o{!+|b;^7~$<$9oul#f2PWTU18mN2l#fgnKL}z#*i$;u#FWQe+Ry^ z|M)ch!)eBAGk&;=FF5)r>5Ao@%Q zvw-Siw`EldqS%Ggu{j%KDm!H>Us4g$^TK@cfwbX+7 z@BGidWIFn|lSx9WmC1FHOYt&>v|b_KRbJ&0F1Gtz=ISl>`CfPGMPbLO66?*r3omWZ zURpLgXzf$+)xzLo`fZz?I%v@QhX})d1=hzrYYKTFU!^}T_sUu zHEo_jkz`u?v-%uSaruPn&p4zie?@Iu5^S}9-ICAK^Cxvi-4sf1T(f=d&dcYSN;iAl zxWjhti&5d$fLpdECEqwhl|^=*KKiz{-L!rAg|Cm!KRx+-?dSR32A|)|J^L%uN!wh)OEj($atGt@@46|SKhIoXDs^r>GQ|#X@8zRe_lG0JRB}1o;K8KcC;PAMbC^e_#In{QCV2J`D4e85o#*JY5_^DsH`<{c_T6 z1)heD8xOKScvq{^_`UwL?X9#+Y$t`L?Y=GfMf$Kf%Zr|XDqpQR#SQN=ez$HuJv+Gym-@=QLTU7zwU$n){H({g08;S3>T(9 zR}^4mvsIsVrfLFSKEEKCNIruM$eZufa`^9t(4Ka>x z6$cwf!=|Ql3Hsax>@UA^IjoJGW7KfJ%xf)Ei|G300t!yE_A*I)XO#$NytI#DB`69% z{AJwtNA*t;1AA5eYz8@n{Sqd}*XkV5z9}PpA}fd4!eQab@5Lv@+}6$KDL7u>rGMvB zLJC8kpU!p}4j-orwxSQ-eJs)9_t6OxS=y3zzCCxLq}-gcY=-Z%n}cm^=Bvv*c$r#V z(WkC4i;>yl9+!$nSda;j<+&u|+NP3%3sr(GlRlTQW?Jp|RZuh0^RjN%e3zw4>0Y~Q zgSN`rM#@gA*(UQKtVimFTSI5?h6|qA(BZUFNf>e@-0QZPuIqLuA1PVktQt_lEHGf-{pHFA@p1l@Wvi^Hk&OaOO z+-7&*Rq19sKhM6s{2t>vuMeyzk8b+*@7#}qN~Ku~-Ug|Cl^28MB`pWm3g$^xXC81a S-)#@fvkacDelF{r5}E*Ly8?Xx literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_up.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_up.png new file mode 100755 index 0000000000000000000000000000000000000000..f18f7189571a297731bbb38eaec7de714a9b2099 GIT binary patch literal 1128 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~q1o(uw z{wDq(4)RMoSKL2_8{FQg?3%kJQ zMs7f}`TYztfZ7B~g8YKlpYPAtxA&Kizt8`^e*S(2vHH^g3=GWEJzX3_DsH`ot4=?X|&G!2`L)G)=GdA4Z{qG+`v$>$* zh*bZ-pTnqQTcyR$a4ntb0gFUKpAkdgbjB~6MGoAV&JgkA^?BBg&AJR6fqV*Vb`9N= z7(TFbGQC)=l`ahh*`8C zM&ZOXX67l%8YiMB>l|Qt`Yz#-Pdd`YN{#v;MxR=n$InPiPvu z?&dvwYc|Mu_&8;p)SG!r;FR!W#_TSkTLEI(>w|)fO)O1sek;k}rnbDI$}WA;)53f6 z<~?TZV!O+Fn?ppyNOhKjXM@?)jG|Dha%7do58f}#+NQJ%(}fT zJC!l2?7qNl7Q2RxISflm&#*hpI_~i~>NKOm#<{mdZFFN8);#OtlGww;p!Mj0s!SXc zgZM;^tf`Xxx)Te`fD3ttnM!NLD>K!i_b}w|f;IVq+1l75BzNV_qy?-Hj@p0a!!1E|73QjsW<)qgoV%Cv;X?L?@B%#e&1b# kuhvI|G;jq#v(^LYYZuQ-ySlwq17=?aPgg&ebxsLQ03f0h7XSbN literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_vertical.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_vertical.png new file mode 100755 index 0000000000000000000000000000000000000000..b1c36ae9a02d25405346b8d45e9a6de19ce54606 GIT binary patch literal 1128 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~q1o(uw z{wD>xhgGb=|*i-1)L}-P_cXSKhIoXDs^IJ?-zO&oAr(|2%#E^Yn%1 zMs7f}*{UMgfZ7B~g8YKdug~xI*O#}Cf6srve*S)j3X{oC85o$Sd%8G=RNQ(y`}L&R z20X325@(L8&9;`-tNXwG&X&!$%h_Xd~NP{|Axep#lnRZg&iyt7=#>{JQz3? z82>D1SbJ*g%lg)fJG1^vIjr(iZ)3Fhe0#U_gFI#q zmg@iWH!?ijw~LRR;gd0A0TYh{(73QR#wor!4a;~KGahcY7e7#z%ixgznDIsr|AJf} zh99jgj8n|?8gyqeD498&XTD%&#;}F6shX*Yan>}(gbUHO3JvVPHaGDFJUn`hhw%)f zx55wZ2YT)cCLQ@M!tsyEAwKy)HP*I}H36J$QSdB;KZQens^Lwty3#_V;$1A2Uyp)?#9)VAEq( z{OjNH(8->iNz-MQlh(Qi8|zkTe05LHB9A|<-@Pra;a0E% z%dzPP7VKe|b>}*puH!q_69Ey$%5{h0OygnGdS7B$+>Ol)ChuSbT}M;2-1vrJc%`Z4dZL6tn7fti91hN!u_ZQ#A)`orNATqUhLf_lMGG0Z zzTFYH&0@!3V~VnkfUk<2aCfzM z-n&2M-+ujOykj=0{Mhvy*Y8#I`sg*C=Ml@i{i8q@kJ1(BzVd*ruhEXWOq^`uX_w&h#wR=sA+x;;sf?{?60d zothKYHfdh^y0oa>w__gMbUkOX^Yhuap}7q~OASryuZX6-*I)W>zpY^H`c%o1)nE9# ek+M|{!6C>Y415pmU7B3Usx>{!wxhJ1b)~asHnD>ZeH}RQ2J|w-Tt-V%Twlj_!!7l}*sw^i0Y@>(C@#4%a9H#%{3XDZ{rUNPXNkOislQ3$Rhj*5@8wt8{k$-1-y}vP zYaSFh94+e$ayVMx10>461vS*NTI(nLKmYdhyT7+3K9pJKzq@~};TTL0ANw8#V}Yfy Ty+7Xdf{gKW^>bP0l+XkKBVDgY literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_up_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_up_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..0aa2b5779d035586666c72f8ae3fcdb38682cdb3 GIT binary patch literal 394 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1_eEIeE`ThGDw%p-Y1d9Iiba4#HxcBykAzzb&Kx<-F z-vhxNjWP!gG0*T=%j(`BED*K$2wzxdn*YkFk!5A-#)eYO?@PV@NOjjc71b4U0*y%H zmnrM5T^p%;^}r!Nvy8@bRja2sJY-%hbfV^fM&lLsD%FIOKxGUJ2kIFo%X}_Qo_nO| zO#T9g^w8BD0WTURGR)*NVDTt*@NalNz4~S1*1~_G;uGelfwesN?4bGguO`cuobx9S zTQg?K7AQNcW7cchvzg(n^;u)~83$U6Voo={4l7DAlur$p0&DuAx`5|-1jm+(GTe<; zvyv4jxEKFf8~(U%P4Bm-d&INVj|Vwe0TrHMt6<>gEDD|WXYyx|K2KLamvv4FO#nJ* BqXqx~ literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_vertical.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_vertical.png new file mode 100755 index 0000000000000000000000000000000000000000..123229ba99a2057dcf716bb79d75b4f649bdb580 GIT binary patch literal 422 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O`1!2l#}z z{s)8SMsCjy-GIy&1|X5=hG5dj^_dY!5D1<7wtEp!8=?7srr_TW@bg^EDgruwD$f$Enho@<016 z+byY;F^;Qe^8Wa9YtwE0bBsX6AW+A@;hWvDPm3gK_rGH7|Fp4PYJ=!vUv7!+n;NWV z%o3(CMM+=qVtncVRtzP6Fdw+oI_LGtr!u>qN-S8pX_j(BNUN%}L1^Y=PKhlI2U#q1 z56o=P;zQQ+ojv94pB$!1ViSB0EMnF99)Eu6?~esjZ~D&8UUfo0Oa1wR!gi5Y=}JYD@< J);T3K0RW!Utq}kK literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_vertical_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_vertical_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..d919814ee83910670b75bcca1545cbf271c97f66 GIT binary patch literal 376 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 literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_lb.png b/arcade/resources/assets/input_prompt/xbox/xbox_lb.png new file mode 100755 index 0000000000000000000000000000000000000000..b7b55df791a184830d799aabedd69c6855570d3c GIT binary patch literal 589 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDgPP#Q1=b=_XT1 zIRpEKiG~a6{$#w?UViep;Q=q_hHG{?%%3bCT+Y117Q%R-J-SkO!vq$Eqo*14nJn^` z`5q_}{7}BX*0$o^bjITf4bL{eozY;ywZM^~mnn`v_GOtvy!B;vi{t|?#~AW?D_pW2 z40rRiTPsGEJN;6h8}MFWPk!}h&l4;%#~6;f9bppL#F}En>SO$pA+O4m`G8D7>Fe#w zQ`+?w*s7Q8Vle-Zry*qjp-gAeQd{|sSznctW7bV%9J1Cu}l1B(I!BZmW= z`A71=wi_GvD}^50|2`t!iZMANI9}sApTRZtrikNa4Ksy8eRLacs_){s%CJ@_bB9;M z?>|4YHl5bantt=O_U8h-!}*~Mzb!j`&nKCyAH2qJsQOJw`M$!P+*%Cx_t)RhFp7)w zQI>kJZhC%c&b~b&SABnHE%y55F8;=&;oFB(xjjp5IIo1(FKsNkZ!Oo!%yZ>$Jm(4F z1l8RAOcU4-ygtElLNcMxr{NA`^%JHib5d_G$emz#%06fQ{`-vTm)Rv2@*QA&aI=|} zMT7MrV{7borU$2Yhlwuu@~W9NW5ryp2J^EGk}=Ju4VU!VSbQER#4+zP-In>LRFg>2Ec7&i(toO68Boc`eL=gb53h6T)SX$RzHG76h3Nai^tM7Q0Yxxt!0 z|o^!|+f@7Fdn8?XM;>;#Wmq*$(Dv`9QG|J@-jADE&TJYD@<);T3K0RVl7 BLGJ(n literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_ls.png b/arcade/resources/assets/input_prompt/xbox/xbox_ls.png new file mode 100755 index 0000000000000000000000000000000000000000..cb6eb93afb0ea96d3939ff7f01499e1da5413736 GIT binary patch literal 971 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD8SHKh49x1DE{-7;x8B~4 z%sZmM!w@jZ;fCb>|66jGzLXM^F1Y3OWBbJqA_@`(M;}jj);YigL-wpJ+A*KxC(haM z(5#_Q(@8C|&iyC5NG zgJj7!Gau7SFnn`9We*h1G$BR|-NJ8c!y6G}M|UEN7JQ;df^|Gl#KvroxVC z3~v@qV9iM2>}6Atb2!8_BhKNUXh-!jhf1X<3=_Ep3K>{)76vrj5vcheo!X-L`;J6* zw)gK9Ele&}4|WL(3*KZ8Z(y}LkPvR%&0t~mz(suuALEA<2gQ%~0y<% zJARkrgk0CN?cqKQ6CQb4>|)r*|KVwL`IRE=^ZY+#CNJckaEx*5a^b^8K2^K+%B=o) z^+NbA2g4;P&yHuka84DNb#RHv_X0yz>s^yBRYlEX7U@5n8Xxs;1%t+tr;Cb{lTJ3& z#LbK4GmPHszJy7kJYMG8y|bC6)$`botUhq!-`kx^%;m^zD2w0^nL%Ue5#`Dv`5rZpR-QOCU39x(Y$^@+|y6vsaDF56*mN*maM2cbj0wF zCu{Gv+5_ zO^X$H8k)NrVs3q}zx8%raP(0Dp@nyEO8(p#{gAmx`DEj5d$|I}7zXhJtPrZ6OJJ^% z-JUk{Q%Bb`>Kt2cZ8XXJ^mE2J^WPttvG=!1gXxo3{%pziED6W0i+jE?GyHj)%Hh*s zky>Ql&wsF9H+Ld-zJkW?Foo#Co6Y!PDF4`44VqSo3}U z7hju_q?3#ruGv3)Di=4|)q(NADqr>3+L*86=NQ*mTkQTfyTR(9kweY8`ETBS+3;HC zBa1`E&i{Wznq?A99eiKgb7Zk`YRQ|qpAER_k=eNXhtJ!>@0X2vj2N8jRi&6KIUO<` zx|w7`7B1j)*vU})+Q{UoNB~3hW!Z-nSzA(>f9Oe71ZbXT-C*OdC$X=FB_NFPHvfTX zqKwQQd-)S=n~pFyoD%N+Xn)?kXCsF(QyRffQ)lFffo zmwA^LuoUb!cHulwP%|$r%;w*})yWJJXE-i0$W<#;pOKJcJMdnADucsg|1W>9tV)RK zXIQ}(z+m{0q5A!15w(VrhMxv}Ju+2I<&2Xh6qtdtcS zM44pn-B2{L;Y zI{T$p8) zn`|7mPGVu5wrC}zjo0bS;BB$9f=pho_rI>$mAH8N(w9E=49eb@*KS)IJ@4GTkLi(u z2`}fYTw=7|LA5+~{#V(lRcx=$Jku+ZNPp$V&Dy>4v1-MpA512t?Olz_{5OXfH_5r~ zmT&Yc`MNqnboQ;&)9f!z(YmU?qVwDZm5%I>+jPza*zOi%a{D+#!^i38%JX~w?~MD` zvWwMTw{txk)2Y=AuY0dvJKzu{{9x}VzbOUk*M4nP6T5uxNYKYu2R0|axwfJ7*38?d zLgt2TH#G{)wOl+k``LEar;>|zI%{8FCi!f=L747mv+%F8oQMT3GzLWR$6_FRqJ(Uj}UUza<)XiYYF?hQAxvXEQ>vQ9-b^1GOh}XIst@&OvP%KQJ>6{Y-Ba46om^kp9smcGi_S3X| z?IJ}bN1bAhI3OM8m^ODaB9kmCwZBCce~;Kprk<5WPg9l(8!R~`((GaWpl)vdTDkh5j6JF+ zHYWawTw46Pjz28j^&PXy7aNwvQOozQ{u;I4Y!1izSL?Vh37f6vRA^ve0(uNc{9sc2 XcEeFt=ffgk#4vce`njxgN@xNAiP+|# literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_lt_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_lt_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..8792f4660837f21c30c7328946ff6ee03cf07c24 GIT binary patch literal 722 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD#k^5Lkb(PKnCH*$S(h0K4U@c2o!6PjlJO!{beepW73Ymt zB@PvvbU7^+UjE#mX~%Jo;rZ%SANPyr&tzb6U_2DWXD+P|a?gL}hX0}n#~p}gNU+=K zaA2qXfhy*0bqsR%b#mD+eC$0`*1)^dH=MU1E@l^_*n=cvhJOF1vS<^Y-I1}J8>#?jYQ-<3)oQ zLw!uDul@lB|3)>2cLz)ta2`0{aN>S|^noN{^^H>*SNk^|2%NgBp`d>E7e*b$uA)ML zXkmeS`yW?}A9($5y86Sf47YZzR<&S^=xV>OZGPyx&4F(o4Ue@sr)G5?4S)A@;c-ry}Gee!-Hht}Q*H!(~*(*#K65ZgKf55EPcEs=m$6;|`s$%eT L^>bP0l+XkK@=iti literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_rb.png b/arcade/resources/assets/input_prompt/xbox/xbox_rb.png new file mode 100755 index 0000000000000000000000000000000000000000..6582f391cd6050d2deb7cc79889774fe751c69b6 GIT binary patch literal 684 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDEaktaqI1^ z+eOU^0&EG71X&92|IfUgGRtg9!-Cy6&i$!=KH*4yOpIUP?V}5UhM|Fm{oEJIV(go> z9{-ayzc+0&!|jVZ4e~4)C3p;5et<2zF^Kf}pgX*;xzq}4Ss$_hUE1RJ5 zO=2~}Jb8z8Oa>ENdY(2!vN&v$*bo%mn01j=?!8kyhr-Gar;joOg)=|8yFgW8R?D=8 z4ATs*4%?<>472!|tA~+3GyU54mkgx!$Yfzw|K7&Gh!uSsvW*{8l>0yy72wud?mBEB=w;L3M^~Qtp-1 z*C$Ug{rELg^_E*?!$U@v>-o8#we$9yZ)Eg(zAZ_!;(u(Q!y4gzy&Ni>FG>PFB{z7o zeF*EQQDa@k=pehYrH4WKy}b3*34#ei`5ya(jF_HHl2YV+A#6T}HTc7xOOAX}Wrqp^ z<`*}FidiYVdc8MQ% zIhc|8CUR4-R`fh$*CgO?ND=C?qPrknlIlAdx4bR*Dw`Kl$SFZUz zIMtu0-dH!m;*gI_T=uv22PIEG{!NmNb~QMs_k$gjqR_yEF8-s7&RJYDee?k& Jvd$@?2>|7+Hs1gM literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_rb_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_rb_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..e8a78e81d10834dfff6c8a2ef111782756167d16 GIT binary patch literal 800 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDfFZ%gLM7(?|1G{!zDM6_9htht`1F1r*ZqyDmTNlX^_m!Y5*U~z4luA8G{BjE zVjGGMzl{q@t=z)dJo6mW9FON+ymvVr(uEyAHdhH7sI4+uRnDMzQFQ%lM!kt*irS2m z<7a)lB@t}B_MPt;gLTd~c3U0TuqSTr%zIh3y=__t?l8V9>6&Q2QACU3uFO7>$h=qm zza0()U+_2S{5CZp#_)mLb;kR175G?~_!ynH-#Rn#L8{_|pUHnuwj?G?hek%G-iLMA9X?P z*OxB2sP|$8!)sQ)8!bE$R$L_w^CRbe(=uPZMxABPHSPlKAJ%;HL?;O(l)q}&x9{M? zK-P$|+S+U9E4hV@?v|}K7h;Gz?ajY!f`?iq+reHIfwNYNw&@BqG*&;eb@)~tynN#n zX8V~u8#MoJWk}#KTe@3>f%RdUYezWKlCwc}6@FZrYJ4|O= zo-nj-Ibft{=`j6Chc+Wq=0bg$rl-@-9$Szo@XWnpFT25XrW7+aUg1EN;|p>aG8iA% zX|?GqYcSrF_!HGJVRc6^L)W2(|4bh~Ft$CI#d2?r()X4=xd&Y5S^r*q`flg%L;nO8 zc&^V14^Q3qAxA^_ZEWs58*abj7nf--I-r^|yPx@+|*^-@H?g8V9?$>Z0%`b i|MjgP~sJ z0DEF^O~fXpAKMuvX2Rmq zLxvnR^KQlkdl=`=R@f25V4^9)n3875$>w3hz{z!B3*%E=f%iL^zIF#N6~r*`u`Mv3 zohfkOCM!R0(S>g^8k70HAB$)Uc)eJN<3K6HK8>W^g2H?iZ0QXX6dBg9%UW<#hk-#y z;K57r@&d<9Mg~Ey4+Yb_9`0@8nr!UA#$0iA&Gs(IwfqSW7`}7wPS_=Nne~PsLwsuY zu_<4x#SfSy7?r$yA!@3vIEAB7fEY8*OTIR0v&*!Rsp?9toEZ@p4z{p@< z_y14u1qt;sMiG_=Pp$V!{^^^%SkJ+Yxg~g&2d{`!!;e3651D@1{g}1liv?P$*rVRL7y<$e3h*%kfoGh}ApM^$ZPlR@d(Go3!g%EZum+f~j_;O11CHhXJXz z>4#rPS6|UTnfF)MHpg+RN_1P-rLf3(-O&NE?W;F#y7O)Bs|~Dja_S1bo-deXwk?R6 zE`C{YiD|aU@|~Mlmwr#$-)pwK_nS@lwgYoHd!+Kun<{LONj}K;>Rcmp*JsH&jC~s) zJf8dIxai&+dc7GtmX$o^{;hqyWc$?gzoIsprZx<0c5C`%Ub`RJ60`3#^D2HZ?!Ujd zUmZQ7{p#z~<9j71p3gS@b~CT?G@}Va2ESwFpVKQB*6cD>?!P|kKBLw7?PGpQz9~?NJr9Y~6&+=dtoiPj?AS6lt8YI_TkH6J`cWSr7ji z7I);C3%He=+-B?((|PH#+&;+jd3xTV&ZvVz=`Cx_8@#V}1eh;wIm`H@*P_rg=vHj* zZ#i>+1=n{Kg}d)$^DF7S75ZD4{HbnnZq3IIiM3|V@}~a}BWI=m3>Wmzh%M~A{{)y> O89ZJ6T-G@yGywpsR^Ju? literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_rs_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_rs_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..7ea3310ae146948d1a34640f135925c3d04cf1fd GIT binary patch literal 1354 zcmb7^{Xf$S6vw~YjE%-T)T9>&T;cDorFh0r8Lmv42mSXZc)N|DE; zOvSipCc31HaZ8x86t!F`YOB@F-R(cPpV#Z0_c^cEIj_%epZrkTHk9TXO#lE;RDa5L z6~X@s0aNvl(&8^Fs3mv@djr7jGg{wb;Hup*G$_n><^M?aq&i>Sa$K&O&z zhtEt$=bG7-lXSY?0n*NvlPafLD#bhO@Z`c}S&0O@3hdTkZhrkwxj^;@%^ra(Os;dB zi^M;?G`!WQWI&$VIgUz$O#v>VA6T&li9v~q-3-E>Tom){Bng~tnG)(SKPt&%HwIlG z=O~#Z<7%ZIcnQ->thWQit$ZIWblh^ZBO}Y^d$z+CU(QP-cRer~L$a%=y~q+`mtuej z&M>RLApDFvSu-PB-TKw~^8S1G0!1xSU^8+xlyYpL2_^cq-2QrUa+g-xNV-_}mc$4m zkj3UT;Ta1?Knbge>9hhBmGMRiAC!TB)g}b%1l1o{Ir2)@kF_^DZB)lJ)%Y0uqc zmppJLWCJc&h$LHdZ>R!HxEd#l7d87LFZ_fgqx#lT{Tr>D@`d5ItK^~yc5J|$E`Z5r z7FJ&SP;uJB!Lp}hbe(~>v*n&(sx^=It^z^PCb!hg5`wS9))Y{r);Y1}(LqHG6WUy0 z=n?-#(@bLCE}G&rV(r0A6AZgLjxF1mydHX4uNUPQdBF2^zGh9Kr5xU#<{n9e7c^7O zLVJbM35MKTa%kRWq^v7taYvjjzr8UC-v{Y$Cp?o$@}9^Z!-PB*M9_Uc;O5dm(@dku z-LCE#m!3F*_J~hMEPg=j%)uN60>k@WRb#{s=r*TxPYbWgeTphE=3Nd;awsrYLfBXv z*AacQ+AaNp`PD&5r`z4izq1gC!9CSS2A3m$dHaPwE63YrzmE?-s)J_IPwH-L%LE~x ztdjA%PR(gXqu*jzO&)lXZul)n4*JUkx$^5^?l7un(1Drc^2EbM`Wkp+WW$B$v+_s6IF_& z`Irb?za@?$(PIfzpIdR$Q4V8CYcsMViw7f6&ZfZ=!j!glcC+M;$@DMr=e;^|+I~h} znGZ{(my23{Q>5;4%Ut-&|2dC1wbC=A-UdklO{G6TYTe)Vh@sHirb;t_>Pw?EZe&I3R^_DQCx~h8rv)4}%(*{22GiZ(L{R&%WHoPMZ0_ zGL3`-(_Oh)E4UIGj-TUX-@wMRL;tSB0kc@19qU)T{5j89MpoTG(DuHeT=i*DmLGQ> zEN1w6pP5T)LlXPLCxQJBuiZEt&&MIkqmkgSF5*DI#)goC4NYuo7!JG*zma>;wIOvo zt4_m8ThaI18dw8vHQW{3z#x=p$}`D0QH|kS)B&asQ~o*##PBk+Zs5rCFuOGG!*j6@ z3?9L6&g(v4?5JC_jDbl?;m_JB3?3N=y4Z5qcu!8zopr08l0`X8-^I literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_rt_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_rt_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..862cbb2972da477462066db04f2364af59f2fea1 GIT binary patch literal 824 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 literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_horizontal.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_horizontal.png new file mode 100755 index 0000000000000000000000000000000000000000..c513f8dfd42999030cd653f5acf589e9ab236bde GIT binary patch literal 1257 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD8fBt*@`27qj-uY)%}`<~nO6K@(=D_3#2=d@AAOxZU4nr*fq|!i(SU*Nz<@GeH1vKNbI_}|uUcF_M?%dpwHHovgYTi>tx z;WIgg^Be#D)h|f-_LkwE>D)CF0^;K^N*RljK5)*b9?Wh z=ilS2OCCIyIrjG8EGb4a#oLbem$#T5v{nmgUvf5C@urySoHgC|f6INjxl&8)&zs%< z?(xaW%FSX~Y0vo8{hwNnyHu!(`DDgLgPO0n@h9;B5Ukp5zb5Ah*P}5=zdin1^7sJ|b?^e1e7&BPi zJK8?ygL)X3)uT9HHue1sU${c*z~k$=I}a^uS$%ZrjV}xmF5GM{P5W!-S&{sSvb)+0x< zfL_Zq%=|xR8go)okuJk;rG_p2B5I6=4`t;Qw4Gjrbt*&~M`oJo#x$f_O;3wh?Ka`; z!aw@+uCFhBmC%+W%DKvRWxu?ZUu?yyBc@k3rSRy~!#hE8@`a7K`< zw9hF~?Z%xY{A>qy9obwTQ@ObL^Y;GJx41qO{$f1Hwe60|9q!wyn;=Pk_1gp#PcqjZ%Ianoc_S!OQ~UAkH*qD~6+up0E+33qmDbc^ zmAL2K&aJus_dSnhn(+LNSK(XbMXIm+6@yk6v8nx75N=Sykn3|RE8WF?Zi`&!szOoK z%|8xsT-EgI)li)y?EE$>?T>MI*VUsDZC1jYBe>Q|`RVNNa_G5gRP3cMs`E5VP+!7v zSFU5UNl5w4@U=f33d(1cFHEk;{q=Ts-^0?=E83*px;$6D+Z@lc8^|7vA%MOvzkx)esxSueDt&U@TnV-v67Y344g_|q)kbaX>iXv49u(&T^VwW4begq z_!?HqB~-C2_@_H*d6i3csz-w8mp$LR9;UG;Fo&3|l-b<#bln|Lt?o+I>23QRt?RZjZ&q5G4V_57ti6!*9~h=5;Xufv2mV%Q~lo FCIDu7MBD%X literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_left.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_left.png new file mode 100755 index 0000000000000000000000000000000000000000..1cab90bf850e5a168de5e7f9685175be39d6f768 GIT binary patch literal 1263 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD}HhFuxIEGZ*dOQ2} zq{kXO4u;Q?34IU%#n_Bf+$?VCGlOhK}6#eC7`}q(91SV34d8`c<^Yi0i|}0)B_vr@xE!>Nf9~ z(|Wa>fyw>X`~DxZBKw(HtWzQjlQo(YR>w~&`5v~D*OaYb()(4L`2#x-bG5A6`e}_# z(i@g13jaau>INF~fpS5(nJhyY{v)9pPbUvEiCWZ3%w;caw z@dOBnES)9GxPkH1>ff!NEONZl_ybPG8%IfTY%wkFihg~J^~Ncdh=)&SI?Q%{{+}go z|FwCS?jM?DyVB=<*mWu>p~|puD>YB-0iS-Lhja;d8Jtq%CkMPE?%+yG+F!Z zOy5<%ejY8;X6)T^Yl5AxaKim{Q+x6<7k|C}=)7~J74L)Nzib;sefN8IuQZq=4Ah?;ayn=LE!+yNH3FI8@vrE+|vHW@W7_S;^( z>*>KucXTAYeRsPotaO?yd9TmtasQphqn9L3Czvf{y%j9>XQHn*W1-v0$w6#8e}p;T z?&n!sQ;OZkfjz7reJ(RAXRn zcqn)`oZ*i#=K+fYUhEP3nH=i7TeiMjo5jz`^m{r(N7jSLXrudU-kGn|+1&GV->tpX zkGNg#Gi8)<$3A&)_NA%7$*6CNmR4$qPepP`L^0pC{vTVl{=N( zeCf-EhA~$mR+)TD*D|6^cHPx~aL?m$&g-1VJ56-mn{Xv2k*`Y&$4XXMj^-8nGCyV(CnpA$;rau7K_?Xc5xDe904RNm$)E4 zZmF=Ygzzpir&$^PC27F!Q`#&4qHalcHNEmgbs%xNE_50mlP|C<`Ksv0zu8Br>b>!X z8hN{L_4I7t@{H~v(*UB@121(db;8V3si!d`m|ep4uDCP;YDG`7KiW`!7%$CA-W?8_ zsl+^z;@)Gb42!}$jQggasuO>?xdjN<@r!CR1{lh^)t z`#bG_e$%)I#ZtS_XFIFt>SPGD5XGtdgF_T9i8+-q?$IDB1vo!Pm>+F5y%mSyj+@!O z+Xy%0i;fuvfl@)S0Af9iT?^nZC;D#44Y~8XBo{5yKM}CNx&<^N;Uf;ZzTnyi43;VxLwN0R8lpFi^Wwe8E+F2N9yZyJ^l4&ve_r zLPd9VE(D7>lWtUfaxu%2C_>0+pkXDx8nJMPgH8YN3ARfKm$+YVBICMRPTQkT2oq?bAV1||g!7j!wcA=u)md{k1nyxkg}U|bj_C3z7y zT<rp~3D=50@k}Q}JZ@zw2Xb?}| z=yk=5+}z%6Awj+$WIJ0l{Y7xBI_*f+NYm0!xJ{9=1i>kW7}^_rINHjO;hlWVC1Y*8&QV MNg;lsos_))070K^b^rhX literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_right.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_right.png new file mode 100755 index 0000000000000000000000000000000000000000..256df0ce0ad555f44a87128a27e37eea478a05c8 GIT binary patch literal 1248 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD+S5D zla?y*I54uN1poh^y?oX3{O(x}Ojh2ad(2qdXV`8(`fd7j2?pi_2A&2+0|vGO|2ZFo zT&g-d?V9IKdu9Xws=E`nzRIim)k+}tR2Ro{cJeMjt%s{PrF zGmItMA0MvjV*bW{C}^t_k3#i|KaEQ5OPrGr=gqQN$o53vGEt-E$-4bV7lw1Us|h5q z@pMQ|)K8lo(9goO#{B2*snT87C%=olqTaQLwa#uq_6d(^a~Qs#5@^^zkMVB3)ef%@ zk2QoC6LuXn)KFc;{DiHcI+fu-jHtu=T!xwKhaWc`@I6pJ=lwmd0t-bOqq~epN*Cv| ze-Jn@(_w*GhU^^CLWXagOcsK78195JUz2MPWjV2Ux5MFtJ(Wk<8fx3Vv&uNc+s%K$ zEO2691JjJte3#i1Hq9TEN?OM61fHzrj{UsU8=(8 z>}2~6yD=JMOn{ojEN^s*UTNRES(-}Il4rFT>bf2r5e5%O*iP+u7AIyukGj!c`n*U?D>EB&; zjY%9UpRT#~Uq9ebish-SmMoi$KkY{u5QRV;8q_EQ*-QC6bAq-Tz?E^SsaNoaemHbACIgy**vjR0*m805z(s zlaHLNf29n|^TJ5+vK)$$4jv8w@C(<;LY3sbh4%rP^VaKbNM`Zn=C}(H?Y+LuY=$BGgmb5YPB=KC6p6ypM jYRzT?2gcL%-!MeZ5lgxA*yaFVv{RY6tUWR*fWBc z3|m^-n1se4Ws_dDbyT z%zd@&tAM_r&0P>Ri_hp}*ukFk(Qs|>cBvDueFH04`DyBz5H~0+F%i=tqt7`ya3`-Nj@i$8hMFgq# zlAe5UE6=l-`-WBESA}Km&%3t^3a5SN9s%ZVe&HGj>nOnBhS*UGm!j^#G;U&M40tvttPmtVSsU?w?r#Hv_WEVPnOQL^XsgJWI)gbw z#JFN5jDd7S`)k@Vvz0>Z%4Qn>n)N?o5h_FVc3T+Kh|X=X(sy@r-&lg5^BGOxplhg# zH!8L6=+p#Z)w!?TVpCq>md)ML&0L~wO&`;A&*APIGTh)U&BBzFPQ;^3cg0%r46{0& zrz*$q(Iotbrmn%;Z^Eew1V7GPliq-*Qv)%e=<>lp{8KlrXmPvC$4IC1D?w+k1yr#G zr0sM5+w54IZ?xUY^>$6O54_g@w3PVsw>uO9u zs(vaz%hrDsd(qZd)xOD55Cjzt9-KQr9?r1VQ# zYJbN|_4N;K;%jIo$%2f=>F6PAO6+}MTTL40SK4&r@ua3W&O;c!dMu~%x$w^>Mnhcn z2~HTXWH_l>YC>X!5R+CGvfoq<9;ir#PwTlhB|5xE!yvbQk|yNgdA}*w9(l$-wC8Vg zA_}aEi6bZkkUgEgtAjm8U=lsT1gq+-gmTpa1Pz(?8MvpHGhiiOE zes?2ZdGdCFjak#hx~xCOGSY{alm;W1@hid+tp;DMR%6E(f_dkqS1Ef$YE1YI_zyCe X9^6trG~2Nve^r3$?CEsNF(l(3%iCPK literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_vertical.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_vertical.png new file mode 100755 index 0000000000000000000000000000000000000000..7b2e6aa84c83290f4af578aa979589783fdf1bdc GIT binary patch literal 1405 zcmb7^`#aQm6vsbbGZ>d)gpA8F>)s5xq%l%v#wFwul9gmyu`@2M2u&ehvlXiid2$() z!K890qcMOMs+Py{G^*W-EOASz6NX@$vL`+5SHvS(4b*TUcw73KQ?=eI$mQ zeONEGquyeaPg^Qg2Fr6?f#lMVyEE0FIlNFjn>B$`gxzt$P~RqbVIa|74nj;bs`I)^ zuOF1#k|H{s>7D5R?LFAG1*#>-*ImGT>Xq-hydzsUNV9-^&=NF=PeqS{!4%6iJ|cKh z8IxnTvMJYNC>9~Z^1p>hS$kbHp(?{RmhkmbsHb2P9=xEl7uRP+ANf|h+yr) z%b&WVf{2=wpw*(`8&ky^m5p)wAZcI7&P@vA+v?TDS?-V_pj*uR6;QCvsV8+l2Z67n z;$G-h-3`a`hE`Vv#@*g7_jWr>#A)1sJ3KPk2*KQfAoS;-5ahh)P?)k>lfqj3IcSF z;A07|7V7+z${6_LV;5CNm#2XqIS%EIg~wiWnA%I{9`-d$W=0@TS-E3K?yy1kz1ndi zR*2+4(z#s*=^?;HXbvdDGa1VpjpeFt7#QlSNO?sHy-#6lYQZM74{b3z?~NYw8bMNd zs`ut}EJ?|?*#$-U^DJd$UM2KvYZoLi1ehnXV}a!J-_py0a}6k7LXf^({9DY)ob;j? z=?Ub|n3eTuJIYf+^L)Ppd_WWnZ0sdXgyu11H$?IDru;(C7KqqDf$SFEQ7E6WU8o01 z2JD1P!xnjI;?(2rXyk1L-l%m@RL`^xcn26eHPad!B<-gNd(_*4W^FdbcQ+bBF<; zON^Y9J290bBpYKJIE}E|3+N}TFkyJ)o&(G&cd`d&B~>4wuC5jyqJ%b|X|#f;n3js8 zBPlBp$FTO6B6*Hx;U}SJ`PivF1xrB5J zwfpT;WLPs6LkqGsLH(=2YwC3r;2oRbmc5*^nKcV@oy?Tx5w zm`H@aniw(0=Rj%6!8f=zd)5L$*;I|Tix6@mj1bNZGSHjlXW-X;u2lw>cb>3Qo1713 z!sdQc1VSMd{o%q7X0MD|g|BKRg~`eOh(JGdObD{*7FOYje^eUcN=ruiTwiNhfMPeL z-lbvxD4F*l8Owfnw%@-$ymgS;XR@4YUft8Ga5cpY^USso^E&US6F&ERg_0GfUa;LO;2QxOJ(=*9+lBGRz( zp4GeRuDSZl0dZ)6Hvg<1la0&c(}~sxRHC;WEZb?#A4yGRZ1b#Ie*J;Mc!%?)sGJ(x z2Evkb1`1cp+{1Inv9{#r$+AFfJ3or%WFlLOAVtJrZuWzAaTo&h(>-E$^o>YilE&|2 zv`ot8Ck_B_`1xsTCFM&=suW`-o-1zxWM8nzj<^}yFl|-RbI#_$O!eieiq4Lub?Gyn z8;zqH`w@xyESRm(6z?6sJ-jH>2K@?akNpSo9i2|4X0-yz9|geO#oM{g@g(aXMRbVD literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r.png new file mode 100755 index 0000000000000000000000000000000000000000..852e140575cdb8919b6399c66c16909dd668b6bf GIT binary patch literal 1286 zcmb7EeKga182|1CW3+?{GlUW?Ekr^SHe4^e?Dj^6?m4?RG|f_^{Je&gx5Sp0MbYF$ zLLu!ZqaspW(WJ^RiqoclW>h?|shsJfG)t&ht6XU(Y9p7D(06GSUJ7==l10 z1*;hP7Z{YP&q@n_s0hvU2=D;lPCj-iQcb1pY5sKY)&El}lWN_dHEmust>pvGLyQeV4SFH4uD?i{mu&6x+f>=X2q1>U(?0bJz5sMu>}KD$}(R;4hB|4U-3x8&Dao#Rl8Dbtz!-ct9$S^~)G7 z8Z5Q_7?E`Hl_$0C*LTUkxhAVg=U47M#_zGxKYe*ZZtFsxKhq2Dq z0tq1tD7jS1eVa+-Z_;*F4fTy%jt;rOO*(~24oV^%u_$ldAleGuoT^i3OQY|GooeLB zs8L#c8wCjsFI=EVJ@)S|;vvgyG>x^%ig-P2^0ZlVCGOG?K=9o)#`Vej`}&{z7ub## zg+PmZ88fi#jG`Lx2@g-6ds;+5l^dF5e}Xkf5&A~J)ozjm^&nxFv{3c6jW2fNOGtcd z_DKjrOFc6`nW2_Q;g_9(lNcH(83whtf?H0I7h>XCdXYxtHShmk+4k2M+~P~X(S{#8 zAex$VV$cb?I4RYgCaN#NIX_SuTg1E|+_!~yZOhlU3Lq|=~o z*Qwwn8zeM0e82}ZmfTCcP(1a*>W~GI)Zay0Xcj%N)H`yhH}YDF}_>6G2C;ud_9wND~m^|C55|%rto~mef!*Yc=D($|0^Nv*u)%x#Tc8N zIN;HXhE;8yw^|I;u|Hgz`PcAHH=UCUNk_h;E2YTJ!7m@Z4gx6h* literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_down.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_down.png new file mode 100755 index 0000000000000000000000000000000000000000..1930ed680138e4f769f3c7113f940c157dca49de GIT binary patch literal 1384 zcmb7^c|6p47{|Y34%dva>lw%M^&dfM-gd81?H8yu8wUWy5bJe)1Eu=EUDz9;7 z&>)j@L@Fs`NTb+g6_s+$*x70S+JE0000>0gZ5q` zLO&z{7xizv!VM7+;p9VP02=e91;I$sZtUsi;~@M$B{GT56&R_jqDeN^%ia5oO@6Y+ za{Ug2pm)#9uwB4Mm76U3)az_d_KD|yDxHj;RYzahnZ6SI^>2^%do`!!PNeO;9$5R^ z6vJgE`!uihP~YMXoA@r_qW)LlTTQD}4yT{Wfr081(4 zsaO>z=f+5c4Wahl*!oq)I?J_A?`RvaY~CnIwk_-paHt3uLzp>Q3nTkn%KQ-gAaOh^ z3BkNX+zEoNQ)(6llIqSIY5)`p;xa*7jF%xAEzYt{dz1_R+&#skc@y9yR)M4yq}1do zW|B_5lxdec^87K zR}wmpl=WNFXM1mk_(n$VwjR&3(A>P{b}dw4V8m02IW?hF<#b#gqOQ{AFnZXsmeI$0 zHoh9P^x=wZ&vD~?cz@v-I=5t;o8@>Xz%qQcGOXoj=(A~>1)-Qx`taN}Z%>R%yvp+1 z?JowV`)p&;&sUs|BmxO2w%nl9yER&WldHvC*x03sZFA5$GL-TzdPeaZ^<8 z-w$I+Z+<#(3Q<|7w^8LsA2S@g9>Ncrgz7#kKbeVat0zMd(m$aOD zw*2z;fyCd><9-el>u4grWhRyyQKGun{;(muR6Ez@W2n|v!UByMqR;o%7(Mu_<@SIX?ME} zGt~GxMXs?n4NC@EEqQ8f!zjl0zZqR_jlOJwGAZ;-ukM-{5Yy9%MsXyk&r%xhci@2K zyV1D)qzJjnXjF`fI%8zrq|_sCO1tWzW^p>#AMmt0lx&0Lm>o+ns0h)#x#=Z7mom5? zCTjuJL-p{EbiD52BsH2;>rkekcP@_Urz}{H@NQlF;%_|oJcq~^BE5>!DEf}0%r$z`#M`n>OX$Ehd2NL literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_horizontal.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_horizontal.png new file mode 100755 index 0000000000000000000000000000000000000000..f7fa95fb6a575c20d4665c62fae109a9eab13f17 GIT binary patch literal 1324 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD8fByS;{rwDAxNg}3ZR+rJaSW-r^>+5# zZHu*d+LSd;r0@N|dhTZHTv1OI$Lephwmx|-=PjpLDcr=|A1_zHe22mO0562v&-p+> zYpS{PN{)B`8E3dn6;Dbr^}EkHYy> zgG%Su>;8Y{S$<_+azQk5BhRb}Rg7NWPcvP-bJFb%v&Ng*ce9tQXfx$v+xq>~rOKrH zTnAnS?lM?ce4xc$bTY5DO^Nlx7;dgdFJ`~}mb`ZU=T40R|KzSO`Z==Ko|gUl`R`p1 z-=8CiapuAWWY*v?V~{eRl-Uq|&*o2@TY zd+ZPA{J@!?vsO(c!Ph{$LA@t~?Z9hS*-s0OF@`ZMsGV_%fh9rlOwE=AMa~Al(h?pH z2A-e#&Wr~PFKuP@&i?1kz&B?Ccfv__C+UQFFKSJ6-!oXON<$Vli$uV_f zLB@t--AW90JEfv5?F$({*f||yycWgyVoKeFZUcrdmd)N9OEZi^Q*e!EG*Ey-VTajENg1?+R~aS7v+A?`71#cWEwTMiReH*rcD27hO?iIA_9; zR>NUulA*Ddrvk@(}< zRNs`X8EamgTXu2DE%O;A8Q)lsU6WyAFn@l_zHg7=k6&*;zo}xZTXbJJ;^Ma1EzybA zIbzBTaXWne-afPP_CKE~ z>^iD0*0PHhTAF(t$TCeSulu>(_P!Xym90~!-Ew!~{*l-rG9z-y%p0!n6)rGtG0ePq zt|P8gsETFHYb~#|kKD>3%QZ|FiIv5$WF6o6Sbk~H8vb>uD`rTrZ(6?Q>COi$7!1Q4 zKb~f}C2zW=)ZggS>D0sDQ=Mz4*4~ZgKT^>8Q@}qaZuaMIXFE^7p0`>mQ!0~djaB;o znI5;f>eHlsqW4;Ax|RM)dT>SX!rT}=D|v&-Ig{>78x^ZtpH`n}P*wIyY>K2%!}>YA z`3=$z3p$=@GyI#x^n~TW5%wATm=65!dJ=7`blit?5%bKQ~H X9K5yVheiUhykhWl^>bP0l+XkKqN-ux literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_left.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_left.png new file mode 100755 index 0000000000000000000000000000000000000000..0d6b849e5e81c439f64aac9b0128d9249f1dd79b GIT binary patch literal 1317 zcmb7E`BPJ86#ZWE0$~fXYfuuTAX`8pB7++{iDCi)r&z57K?wqa2qBWHk3~%t5U7l7 zlA4GRL#$YFL1b~E;Kou=5CX9TQ2_%OQg)io^dIP*x%ZqibMBnq&d))#06lHIHUL16 zO7Uf=5&3tqX!TXf^5@h5M|PJ>RLKU|<=CblNLg3k%)&n2m>| zTi@Bgssz}#nyXtu*VpWqFSejRr9>YTpSD7xN>_4-LojoE2WQ_m9FnztkU*BzaJ%9W zEaud~(0;hhS3d}XNYCK1H~@Zce$JqIBA#1HkBOffNl!A!XGO) z{uKJyVbPd(bCieb;luM*TmxEj91qZ!PM9UU8VD`Be{_VbC#z_TUO=0))$J`WvIUY$z|aoNpBK#MF;Ie`Q2qAtBSeWI8DUo?p0**|Hd zZJ`gRH$E3GCY#G3_<}RRu>@$j3R2f2&{yuU`HvlxZ4kDX(gKz1zs5_m4x}}oL&Lqi zJ}w9^G3l%&pCwR@i9~-GlFbRs6NR>GsSyC$bjS|$v=^Gfj;k=`UnW;G!R6z52H-;e zHPYGS!GT!=OlN5;a~H?Z)fz(qbQqbA_t9WlCh2V$hdg1HjW=lG1cw6G z(Exie5WtHCzP1&*ExF9?B)tQ3n9v-Y(Z+o+Y%yH)Gy4(Bw0*(vgRi6x{XUxzzKb zTUXsjy)zGMr9?6fL>r>McYU5L4ta|2`;Kw(#&~}C_2fu5(Tjz%V=9bm*HNE&*F8wQ zgifv-$#E)D6$jr24Z@uutW8y#MakG?jQn~pM20Dvi1m=Y`Te4GbNmDGQen51y*AkD zW+2&Ln0U!w8C$v;*B@RpjR>-yvAJR+dNEOpA-2?y^O3qu%W0zOSO-;}`6?1sVS>ik zL^DzPX=vd-JvSRt-!KS=DGSt(Z`@`Z(n9W(;gx;?BCBVKNhe>XvRXBNY2}+6p4jlPgXb8bEAM_? zmFy0(pBzU8f_aIqpOOG!6?UKn(p}f|6O1gA3Pz^ZAUQ>bVVY@!#m@D8P%p@`DIDH0 zb)+4=gzBsWB~owxEZuyby?s$qoabM@d42`M=ncs!kmP0Rm#jp%{MR5%R3H+blHa_` TE;pxIT~vVTNAtbq&CK`*JA-Cm literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_press.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_press.png new file mode 100755 index 0000000000000000000000000000000000000000..faed4c47ff53a9fbc5446df2645d3347bb4ab70d GIT binary patch literal 1364 zcmb7^`#;lr9LGO1%O%WA5p8q7G}kza5?z`K2 zpPbOYQijX>SALm94#il{J)QuxUs^AXLdbUqvOi^)?EjS9BtPH4&R>yFnwk3of>Hu6 z;oRxHwk*+M?sOdz5J{&v@=v#Yy*(*8JYkKX{OT4pXzyPNo2F#(+$^F0wrEK4ctA2k zT6^b(d1v|d_X7H%_pls*h3H6wO?CQ%`nm2L=A*<%7|u7CR0vMorYb4tN@lX4X5{Tj z5n`g*X**h}13wptXcT%$GRjh)&arxRiqU57@36(fh59|8jd7C+A^=c_sZ%C7gLFi( z1wR^8^$=3eN1Dqvl>gGD34%?=OewNm_2Dx{;5C*p=SI)4V7y61K*gPO9%k_Df-8sD z{>`p5@SHjv=p)1QS|f>{nMl8yzS-d`umcNA#@0L7PjEX@*Ltm%?Zq+Jdezl5_{skI zYiF@gTff7|A6QQ=Y;njoSScRA7RUaP zY6I2&;hu&?o3{f!d)$meZfp1DXF$syTM;TqzF%lh+6p;$sZ03es9Aykj<7?$zUO4#IQ|G3P5v9`qauYX0Xj~cuoL`v(2)&rK}PTg)Pg+Ot)x$p z&KuVs1=Bx?zTa(ZfM@$JWkUJb_rQSfq72m+F$eGt0shGLUNOx|1HY&DzMNO1o2#x|X6?l)w0x~J&0=zxXn%9Jy<(ZAg zOB(iL$u*SIS*KX&z$)ve`tKA)$*#EF^XqOn zJL)Gq6KAxn$BRFfRIwC;f2nRWYC8~nksABv>qn-GnZDZaSk;%EbV}bI<33#$_XW;u zB(1#Nd`$ECa83E#y*jkW^7!e>0Gq|N$132}_)0RZf6~{+(F$*6n!a$1;<6d|%!q+2 z_)6S_vY%2YZL6c literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_right.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_right.png new file mode 100755 index 0000000000000000000000000000000000000000..0473f3ac6a473077ff8a4dd2cbc0379af72e8c08 GIT binary patch literal 1321 zcmb7^|3A}v6vyBDjLm0^;X#`(-Hj`^d|RZrbG0$#`+T{KL{sUiL@I7|uYFJ ztE;*_qBXJW`ck$`CMjPZ`I@1aZmua3+vTqNAKdeJobx*8@i?!?`RP1!SOH#moDmKH zfcM_T3|1-fB{k5hx+uN0q7t=(41WdyHTjxKj=E}gVEKi5tpA@-nN;frf{ah9j&LG4 zAS5w=CpYk`2lFp=#(%YoX#pC^Ga0H&=e(JW&>x5A%4YMX$XbXqbk?dvv2)I+Jl$DFy{nLpA*{-XkuF;NYOfh~F&4#K{$!`NKtOuirQPpfB->^_V270Nys({) zTuhttr9|sGJ2{S`*?z~4Mn~Ao-3;Rz(EP>sYn{^L(^PFjyR}vHFHg?~fm4tC8L5dS zBiZ|a(oFfDNbyOFL=`uLMPISZ15q!yC5D4>2neyDVPG2B5s9*GTYDQ$|!-~pjJNKpuQPT zE^e7DC`)(O*QfyKcNZlkL!SNS>0md7+q86ka_+Y?n5CTRL+SIHb!bvYsh%{N1SPE) zyBKI`iSHv#TiJsoVwfP~XV{0Y!v*kU8II6|fyjggEn z=ro>{u)UcQv6(=Ile9F^w&6KA;XK2JdPxKe9=8Y!4psDQ z@rWP8HD#(pavCfQ9Z-B*Oi8V{S@e8<8*(|Vo`>Q;NiDe5CR;NAF~_$KMai{$y+9)u zPU02bS`zi6DrkSYxXFXz))3#4#vuU?jEe3dL}NIx)}h8>c4W)qz+rsmb>=OX01FoB z?wdGC6ARV(#KJeMsw+EXz|h6DrEjZuUuko*zPE+Z*g!2u@XjwHhI1!kgG4>yOm3)F zYMEVoRG;TmXJ2B~9GWFTFS`Wy&zW%!eLoPpwJJ8bs$U-s5i5UaTo?w$$Vv2dU?hb4I6fn~*@D!3lWRi6Dz!|oZ#KYAbtzly_^=f6m~ z^lXtgsE~aU>tsJxXWch!9BdixezhbIcW}+Cc3f3Jq7#LcM@n-<=cetf|AUDB-Y-Ov YW2=fqv|cebLPx# literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_up.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_up.png new file mode 100755 index 0000000000000000000000000000000000000000..98d7d1e4a948176cd0b4fe64a3fe91bfdf95a710 GIT binary patch literal 1370 zcmb7^|34FW9LGP~e3@m&%9pfV7S<^ZMJr)*H<=pwvV^W$oNp`A=}^R?YtEONiM}kY zICqrq5oXO)L~g!+SqO(%Ov$&@mQME{-0Sgpzn|~-HDr!}YqJGwdoP=0Nl@}%n8zRR6Pcl!E&wDz=iu3MG3yf$f{^K|n% zbL|kRJz;Zcy~W6h_b5KFWoo;uIX!lvIMQPVW_dFec}AKn9Zo9r%@=-D`&)so6&dFQp@NHz6U70T@?HtrB32^cEiy zDuP2Ii%T*_4v7ie5m#{ucjTa$kU8Sg0b^h_N9Dobz`O!3{cXMuF3p;cg*OAdH~ai1 z*1+Ag^m5mS1huk;Ft0j6T=hu-!$QTqUHov~0G?idwv&`pDS(UFN&H)b^PA&Z*DG~_ z|0{g|B0W&&8E}x7vWNYd#Osq=tU(d2FS=o}-u6>Qr7=*3ETEk@gn{>>(J#QaCvw-z zl3qF7gZocK+X+?e+*tuRHVI*g9d?Ly|8Rb1lM?P1ze?NHPM2ssv|Iw1;e>hh(xL`S zahL0qO*qnE|JS!s-ZXE_*=ZI^Pv{Au%m+NFcOVusOrkxuUG(Vy=p)=1GQrqi-go;! zgBhBoqS-v1jPV3l%*_D#)zq!j6|4K}sM=&mld*BA@Rvu=Z|WL~?+icZQ87*YN=6x& zX;~GfO?*IL1Oj~=%alKE{4j2aRXa!uyJ5jG9DL>Mgmp4YO~jMV{Uekl_4l? zBZJp7my4!_tBN#?c77?8LSt&}LyGA^&bbq$sok&1aDQv$Xn-wLh8lSQ3lH+7o2gdw0`je}?&Us&lzGPVrp)TZbx z!~PBd$3hJ>fsZ*Da;SO@#ykeuRn}_OkWN(D7RaAW#m!8}@s|r)>WowC%6Mo)GR(}c z1y#V+TG{JMhSfYcznLGXij_rjUcgKi<3j<}N8QDl9SttuPHnlX`YUI5iOqKme&Z3p ziH+coB)TZ2v5S@E)oLB>UV#8I^0qQ*7<=5lJTi3kN~z>hwr52q%+_>YRklGsfu8?; zr&|l9yx6~7w}N+|>N~vIQr56sz?hgRQtqwkit>w>QSNv(2l1ZI4P54W-fkemVkCyx z2&&FKbL`8M#_E^D{ckJjn0xk}Owh79i;fnuy=uSIfR& zU6eCO9o%suy9;(YlPOPuKf{zJg$eSdk$FN9Vaj<2INMg->?~w;qgT9l^f0wIe1J*| zFLi8R=Ct>&1ng_}*w8kFICv@}T+fc#y@XbcW|Z$wtd2F(qI*wgNa)KSg_T=ZOn6^n zmG=)9U{hqin6xg09uqm+5qVmFEYNrn`_mOv^H%KZ<*V=V1}EN$#UVqukQ$%Q9!Q%s z>x)V|eEml1pktF(!O76rGqmfxWBeF)i?L611g>8l1};AcA&RzJ?6lXRhMV2{k%;TQ z546!QHjS!@NeAYVTaSXLHX39S>nW_D8eCtXpHs7ik7nQ;6-0=Ugw^M6q?a>`&&q4t zBVE|@#G#7wzGmq7J3RtcU#SpTfE&0txgs(6Y1=t&o`HliMfuntMHC|=U_;ER*{1@GiiuwQm literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_vertical.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_vertical.png new file mode 100755 index 0000000000000000000000000000000000000000..5d2cfe94bb206e2eba7eccd410911a2d46f12c35 GIT binary patch literal 1465 zcmb7E`#;lr9R7S8GYm^*a@&-OoZP8{Vj`{P5;n4Abiv$CSxk(txkheXmc+3x%0f1* zD3^JmagH68N~9<$p_FQ*)y~eJaNe)i`+45a>-9XpJU_fo`FeY5sOhNz05piZ-Tf2{ z|F4g_epirE1i^~{fS_1i7N)Gww!WkQ5BdKYMMz;^BV$Dsn44tpi0z>JM-2f=BA-WR+n4=#q#TH+~qv3sT$h0MSr!?_h+sf$6%9Sq;*rbje zo_ydr>PEfElr3Ozuw38O{80F^2VB;8(NO78996Cx$wed*4b&P-RZ5cl-Co*2?!C+=!w=G6LeX#ed^R;hYz|2T`F`FYVp;nZ91Z86m ztutEWF~(Ya@6T_~EUqW*G{$BCen(KKxG(DGq{UW(rw{3zo7vwC5` z&IcjD$Qoa3ldz;}eRv!JUtxbu#r!oV1cZqrdj+0Z4Evrf6hO%eRz|FAX~ z#9||n`3UYS_Q(Llq|y1lymAQ7?cZhuIE~v{oY~N)_&-ayL=4bNj`KgMM)q-rOd_G{ zntU(%VX`VRn6M7o$aSeSgNJYD)}Kcv+LZ!z-zpl2u`Dl9QMS64t>nvH=4}J5{h z*5@l(p>fjyPNVIH!MXDdJUn7Z%>*!-QzfW`*nijv20AWB5D3P^oC9&J)7^7D_72;ygAjMF5gY zb!ufTFQX=xb7YRoS_K|v3`aKZ#fejBSj{7A6XS}`om2BEnk$(L2rlFoyWz?8YBWbGb8%hYmm=%Wdxb$f!eX9y<}blj zO2vWpF>SB*bkT3$tQwEMqwqM->4KfjNw0aq6afpXV;-`quaJ~APFoigsAyu;KS5qi z?zyp%y`mV=QyP6Q?`Gg_$Ye%*Z0@zqm5b8RGsU5w#VNPqUSyhx%&FahcRc1r#^pg~ z-&F!PoJ8f#{^%x4%%4_17fc%$TE7oh>1A(7nbsioXofWopNV_LFY<)@lNhF5@2{9G zLo-TwGEtQMl<>3Z8Bp5rkv^F`)77l{&w`|~kmQpU)T!k>Zn-PbVC3Nd^3d$h*icdj zxvx!QqWNVW<)u16S=BOaMc&-}U-@O}n!ss1JZ&o+t?h1uXhCDB5vc}}O-_KZoysQG zq^Sev>_s9T%@cnTN?Hzs;p3ZXu`d!;lmDqgSA8|6NpdH5|BZvw>#dc#5Ywbo+luB Lc)Q+Wc( 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 Date: Fri, 7 Mar 2025 22:23:59 +0100 Subject: [PATCH 03/34] more robust controller connection handling, just in case --- arcade/gui/experimental/controller.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/arcade/gui/experimental/controller.py b/arcade/gui/experimental/controller.py index 040f12e43..54be7245d 100644 --- a/arcade/gui/experimental/controller.py +++ b/arcade/gui/experimental/controller.py @@ -1,3 +1,4 @@ +import warnings from dataclasses import dataclass from pyglet.input import Controller @@ -128,11 +129,19 @@ def __init__(self, ui: UIManager): def on_connect(self, controller: Controller): controller.push_handlers(self) - controller.open() + + try: + controller.open() + except Exception as e: + warnings.warn(f"Failed to open controller {controller}: {e}") def on_disconnect(self, controller: Controller): controller.remove_handlers(self) - controller.close() + + try: + controller.close() + except Exception as e: + warnings.warn(f"Failed to close controller {controller}: {e}") # Controller event mapping def on_stick_motion(self, controller: Controller, name, value): From a81ca2be516c79a547cda95291488942effe1bb6 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Fri, 7 Mar 2025 22:27:35 +0100 Subject: [PATCH 04/34] fix lib imports and extract UIFocusMixin from UIFocusGroup --- arcade/gui/experimental/focus.py | 82 ++++++++++++++++------- arcade/gui/experimental/password_input.py | 4 +- arcade/gui/experimental/scroll_area.py | 11 ++- 3 files changed, 65 insertions(+), 32 deletions(-) diff --git a/arcade/gui/experimental/focus.py b/arcade/gui/experimental/focus.py index 09e512f9b..33bd7e402 100644 --- a/arcade/gui/experimental/focus.py +++ b/arcade/gui/experimental/focus.py @@ -1,3 +1,4 @@ +import warnings from typing import Optional from pyglet.event import EVENT_HANDLED, EVENT_UNHANDLED @@ -5,26 +6,23 @@ import arcade from arcade import MOUSE_BUTTON_LEFT -from arcade.gui import ( - ListProperty, - Property, - Surface, - UIAnchorLayout, +from arcade.gui.events import ( UIEvent, - UIInteractiveWidget, UIKeyPressEvent, UIKeyReleaseEvent, - UIManager, UIMousePressEvent, UIMouseReleaseEvent, - UIWidget, - bind, ) from arcade.gui.experimental.controller import ( UIControllerButtonPressEvent, UIControllerButtonReleaseEvent, UIControllerDpadEvent, ) +from arcade.gui.property import ListProperty, Property, bind +from arcade.gui.surface import Surface +from arcade.gui.ui_manager import UIManager +from arcade.gui.widgets import UIInteractiveWidget, UIWidget +from arcade.gui.widgets.layout import UIAnchorLayout class Focusable(UIWidget): @@ -59,7 +57,7 @@ def ui(self) -> UIManager | None: return None -class UIFocusGroup(UIAnchorLayout): +class UIFocusMixin(UIWidget): """A group of widgets that can be focused. UIFocusGroup maintains two lists of widgets: @@ -79,11 +77,12 @@ class UIFocusGroup(UIAnchorLayout): _focusable_widgets = ListProperty[UIWidget]() _focused = Property(0) + _interacting: UIWidget | None = None _debug = Property(False) - def __init__(self, size_hint=(1, 1), **kwargs): - super().__init__(size_hint=size_hint, **kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) bind(self, "_debug", self.trigger_full_render) bind(self, "_focused", self.trigger_full_render) @@ -112,21 +111,35 @@ def on_event(self, event: UIEvent) -> Optional[bool]: return EVENT_HANDLED if isinstance(event, UIControllerDpadEvent): - if event.vector.x == 1: - self.focus_right() - return EVENT_HANDLED + if self._interacting: + # pass dpad events to the interacting widget + if event.vector.x == 1 and isinstance(self._interacting, UIBaseSlider): + self._interacting.norm_value += 0.1 + return EVENT_HANDLED - elif event.vector.y == 1: - self.focus_up() - return EVENT_HANDLED + elif event.vector.x == -1 and isinstance(self._interacting, UIBaseSlider): + self._interacting.norm_value -= 0.1 + return EVENT_HANDLED - elif event.vector.x == -1: - self.focus_left() return EVENT_HANDLED - elif event.vector.y == -1: - self.focus_down() - return EVENT_HANDLED + else: + # switch focus + if event.vector.x == 1: + self.focus_right() + return EVENT_HANDLED + + elif event.vector.y == 1: + self.focus_up() + return EVENT_HANDLED + + elif event.vector.x == -1: + self.focus_left() + return EVENT_HANDLED + + elif event.vector.y == -1: + self.focus_down() + return EVENT_HANDLED elif isinstance(event, UIControllerButtonPressEvent): if event.button == "a": @@ -225,6 +238,7 @@ def start_interaction(self): modifiers=0, ) ) + self._interacting = widget else: print("Cannot interact widget") @@ -232,11 +246,20 @@ def end_interaction(self): widget = self._focusable_widgets[self._focused] if isinstance(widget, UIInteractiveWidget): + if isinstance(self._interacting, UIBaseSlider): + # if slider, release outside the slider + x = self._interacting.rect.left - 1 + y = self._interacting.rect.bottom - 1 + else: + x = widget.rect.center_x + y = widget.rect.center_y + + self._interacting = None widget.dispatch_ui_event( UIMouseReleaseEvent( source=self, - x=widget.rect.center_x, - y=widget.rect.center_y, + x=x, + y=y, button=MOUSE_BUTTON_LEFT, modifiers=0, ) @@ -253,6 +276,11 @@ def _do_render(self, surface: Surface, force=False) -> bool: def do_post_render(self, surface: Surface): surface.limit(None) + + if self._focused < len(self._focusable_widgets): + warnings.warn("Focused widget is out of range") + return + widget = self._focusable_widgets[self._focused] arcade.draw_rect_outline( rect=widget.rect, @@ -295,3 +323,7 @@ def _draw_indicator(self, start: Vec2, end: Vec2, color=arcade.color.WHITE): @staticmethod def is_focusable(widget): return isinstance(widget, (Focusable, UIInteractiveWidget)) + + +class UIFocusGroup(UIFocusMixin, UIAnchorLayout): + pass diff --git a/arcade/gui/experimental/password_input.py b/arcade/gui/experimental/password_input.py index c0cbd3ece..b778bee15 100644 --- a/arcade/gui/experimental/password_input.py +++ b/arcade/gui/experimental/password_input.py @@ -2,7 +2,9 @@ from typing import Optional -from arcade.gui import Surface, UIEvent, UIInputText, UITextInputEvent +from arcade.gui.events import UIEvent, UITextInputEvent +from arcade.gui.surface import Surface +from arcade.gui.widgets.text import UIInputText class UIPasswordInput(UIInputText): diff --git a/arcade/gui/experimental/scroll_area.py b/arcade/gui/experimental/scroll_area.py index b33c90778..31d44e0eb 100644 --- a/arcade/gui/experimental/scroll_area.py +++ b/arcade/gui/experimental/scroll_area.py @@ -6,20 +6,19 @@ import arcade from arcade import XYWH -from arcade.gui import ( - Property, - Surface, +from arcade.gui.events import ( UIEvent, - UILayout, UIMouseDragEvent, UIMouseEvent, UIMouseMovementEvent, UIMousePressEvent, UIMouseReleaseEvent, UIMouseScrollEvent, - UIWidget, - bind, ) +from arcade.gui.property import Property, bind +from arcade.gui.surface import Surface +from arcade.gui.widgets import UIWidget +from arcade.gui.widgets.layout import UILayout from arcade.types import LBWH From 818c5ad16077c061bdac3a535edd29ef4051e8bc Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Fri, 7 Mar 2025 22:43:22 +0100 Subject: [PATCH 05/34] make UIDropDown support controller --- arcade/gui/widgets/dropdown.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/arcade/gui/widgets/dropdown.py b/arcade/gui/widgets/dropdown.py index a162f5b66..376a95ef9 100644 --- a/arcade/gui/widgets/dropdown.py +++ b/arcade/gui/widgets/dropdown.py @@ -9,13 +9,15 @@ from arcade import uicolor from arcade.gui import UIEvent, UIMousePressEvent from arcade.gui.events import UIOnChangeEvent, UIOnClickEvent +from arcade.gui.experimental.controller import UIControllerButtonPressEvent +from arcade.gui.experimental.focus import UIFocusMixin from arcade.gui.ui_manager import UIManager from arcade.gui.widgets import UILayout, UIWidget from arcade.gui.widgets.buttons import UIFlatButton from arcade.gui.widgets.layout import UIBoxLayout -class _UIDropdownOverlay(UIBoxLayout): +class _UIDropdownOverlay(UIFocusMixin, UIBoxLayout): """Represents the dropdown options overlay. Currently only handles closing the overlay when clicked outside of the options. @@ -37,6 +39,13 @@ def on_event(self, event: UIEvent) -> Optional[bool]: if not self.rect.point_in_rect((event.x, event.y)): self.hide() return EVENT_HANDLED + + if isinstance(event, UIControllerButtonPressEvent): + # TODO find a better and more generic way to handle controller events for this + if event.button == "b": + self.hide() + return EVENT_HANDLED + return super().on_event(event) @@ -188,6 +197,8 @@ def _update_options(self): ) button.on_click = self._on_option_click + self._overlay.detect_focusable_widgets() + def _find_ui_manager(self): # search tree for UIManager parent = self.parent From 7489e4cc5c7e14fe211449118b1ea174eea6eabb Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Fri, 7 Mar 2025 22:50:37 +0100 Subject: [PATCH 06/34] wip slider support --- arcade/examples/gui/exp_controller_support.py | 11 +++++++++-- arcade/gui/experimental/focus.py | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/arcade/examples/gui/exp_controller_support.py b/arcade/examples/gui/exp_controller_support.py index d2f97cec5..fd6fe55be 100644 --- a/arcade/examples/gui/exp_controller_support.py +++ b/arcade/examples/gui/exp_controller_support.py @@ -5,11 +5,13 @@ from arcade.gui import ( UIAnchorLayout, UIBoxLayout, + UIDropdown, UIEvent, UIFlatButton, UIImage, UIMouseFilterMixin, UIOnClickEvent, + UISlider, UIView, ) from arcade.gui.experimental.controller import ( @@ -156,14 +158,19 @@ def __init__(self): self.controller_bridge = UIControllerBridge(self.ui) - self.root = self.add_widget(ControllerIndicator()) - self.root = self.root.add(UIFocusGroup()) + base = self.add_widget(ControllerIndicator()) + self.root = base.add(UIFocusGroup()) + self.root.with_padding(left=10) box = self.root.add(UIBoxLayout(space_between=10), anchor_x="left") box.add(UIFlatButton(text="Button 1")).on_click = self.on_button_click box.add(UIFlatButton(text="Button 2")).on_click = self.on_button_click box.add(UIFlatButton(text="Button 3")).on_click = self.on_button_click + box.add(UIDropdown(default="Option 1", options=["Option 1", "Option 2", "Option 3"])) + + box.add(UISlider(value=0.5, min_value=0, max_value=1, width=200)) + self.root.detect_focusable_widgets() def on_button_click(self, event: UIOnClickEvent): diff --git a/arcade/gui/experimental/focus.py b/arcade/gui/experimental/focus.py index 33bd7e402..27610365c 100644 --- a/arcade/gui/experimental/focus.py +++ b/arcade/gui/experimental/focus.py @@ -23,6 +23,7 @@ from arcade.gui.ui_manager import UIManager from arcade.gui.widgets import UIInteractiveWidget, UIWidget from arcade.gui.widgets.layout import UIAnchorLayout +from arcade.gui.widgets.slider import UIBaseSlider class Focusable(UIWidget): @@ -112,6 +113,7 @@ def on_event(self, event: UIEvent) -> Optional[bool]: if isinstance(event, UIControllerDpadEvent): if self._interacting: + # TODO this should be handled in the slider! # pass dpad events to the interacting widget if event.vector.x == 1 and isinstance(self._interacting, UIBaseSlider): self._interacting.norm_value += 0.1 @@ -277,7 +279,7 @@ def _do_render(self, surface: Surface, force=False) -> bool: def do_post_render(self, surface: Surface): surface.limit(None) - if self._focused < len(self._focusable_widgets): + if len(self._focusable_widgets) < self._focused < 0: warnings.warn("Focused widget is out of range") return From 91ec3971bd08c5c5f586ed71145e217d3bc1575e Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Fri, 7 Mar 2025 23:07:33 +0100 Subject: [PATCH 07/34] UISlider dispatch on_change when changed via dpad, this is only a workaround --- arcade/gui/widgets/slider.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/arcade/gui/widgets/slider.py b/arcade/gui/widgets/slider.py index d04088594..390f9c3eb 100644 --- a/arcade/gui/widgets/slider.py +++ b/arcade/gui/widgets/slider.py @@ -95,6 +95,21 @@ def __init__( self.register_event_type("on_change") + def _change_value(self, value: float): + # TODO changing the value itself should trigger this event + # current problem is, that the property does not pass the old value to change listeners + if value < self.min_value: + value = self.min_value + elif value > self.max_value: + value = self.max_value + + if self.value == value: + return + + old_value = self.value + self.value = value + self.dispatch_event("on_change", UIOnChangeEvent(self, old_value, self.value)) + def _x_for_value(self, value: float): """Provides the x coordinate for the given value.""" @@ -110,7 +125,8 @@ def norm_value(self): @norm_value.setter def norm_value(self, value): """Normalized value between 0.0 and 1.0""" - self.value = min(value * (self.max_value - self.min_value) + self.min_value, self.max_value) + new_value = min(value * (self.max_value - self.min_value) + self.min_value, self.max_value) + self._change_value(new_value) @property def _thumb_x(self): @@ -181,9 +197,8 @@ def on_event(self, event: UIEvent) -> Optional[bool]: if isinstance(event, UIMouseDragEvent): if self.pressed: - old_value = self.value self._thumb_x = event.x - self.dispatch_event("on_change", UIOnChangeEvent(self, old_value, self.value)) + return EVENT_HANDLED return EVENT_UNHANDLED From 500d2401a80e95f24dc657bef183d6a79ae08b54 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Fri, 7 Mar 2025 23:07:49 +0100 Subject: [PATCH 08/34] reset focus when out of range --- arcade/gui/experimental/focus.py | 1 + 1 file changed, 1 insertion(+) diff --git a/arcade/gui/experimental/focus.py b/arcade/gui/experimental/focus.py index 27610365c..c45dd2567 100644 --- a/arcade/gui/experimental/focus.py +++ b/arcade/gui/experimental/focus.py @@ -281,6 +281,7 @@ def do_post_render(self, surface: Surface): if len(self._focusable_widgets) < self._focused < 0: warnings.warn("Focused widget is out of range") + self._focused = 0 return widget = self._focusable_widgets[self._focused] From 4711cf52b03f8bc4ac69f4ece1a54290d1d50802 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Fri, 7 Mar 2025 23:08:08 +0100 Subject: [PATCH 09/34] controller example listen to slider changes --- arcade/examples/gui/exp_controller_support.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arcade/examples/gui/exp_controller_support.py b/arcade/examples/gui/exp_controller_support.py index fd6fe55be..d99cf2cf9 100644 --- a/arcade/examples/gui/exp_controller_support.py +++ b/arcade/examples/gui/exp_controller_support.py @@ -10,6 +10,7 @@ UIFlatButton, UIImage, UIMouseFilterMixin, + UIOnChangeEvent, UIOnClickEvent, UISlider, UIView, @@ -169,7 +170,11 @@ def __init__(self): box.add(UIDropdown(default="Option 1", options=["Option 1", "Option 2", "Option 3"])) - box.add(UISlider(value=0.5, min_value=0, max_value=1, width=200)) + slider = box.add(UISlider(value=0.5, min_value=0, max_value=1, width=200)) + + @slider.event + def on_change(event: UIOnChangeEvent): + print(f"Slider value changed: {event}") self.root.detect_focusable_widgets() From 9eaa54286f601a80bb12007da6288c6af6cadcef Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Fri, 7 Mar 2025 23:15:28 +0100 Subject: [PATCH 10/34] fix focus out of range --- arcade/gui/experimental/focus.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/arcade/gui/experimental/focus.py b/arcade/gui/experimental/focus.py index c45dd2567..5abb775a2 100644 --- a/arcade/gui/experimental/focus.py +++ b/arcade/gui/experimental/focus.py @@ -154,6 +154,16 @@ def on_event(self, event: UIEvent) -> Optional[bool]: return EVENT_UNHANDLED + def _get_focused_widget(self) -> UIWidget | None: + if len(self._focusable_widgets) == 0: + return None + + if len(self._focusable_widgets) < self._focused < 0: + warnings.warn("Focused widget is out of range") + self._focused = 0 + + return self._focusable_widgets[self._focused] + def add_widget(self, widget): self._focusable_widgets.append(widget) @@ -178,7 +188,7 @@ def detect_focusable_widgets(self, root: UIWidget = None): self._focusable_widgets = focusable_widgets def focus_up(self): - widget = self._focusable_widgets[self._focused] + widget = self._get_focused_widget() if isinstance(widget, Focusable): if widget.neighbor_up: _index = self._focusable_widgets.index(widget.neighbor_up) @@ -188,7 +198,7 @@ def focus_up(self): self.focus_previous() def focus_down(self): - widget = self._focusable_widgets[self._focused] + widget = self._get_focused_widget() if isinstance(widget, Focusable): if widget.neighbor_down: _index = self._focusable_widgets.index(widget.neighbor_down) @@ -198,7 +208,7 @@ def focus_down(self): self.focus_next() def focus_left(self): - widget = self._focusable_widgets[self._focused] + widget = self._get_focused_widget() if isinstance(widget, Focusable): if widget.neighbor_left: _index = self._focusable_widgets.index(widget.neighbor_left) @@ -208,7 +218,7 @@ def focus_left(self): self.focus_previous() def focus_right(self): - widget = self._focusable_widgets[self._focused] + widget = self._get_focused_widget() if isinstance(widget, Focusable): if widget.neighbor_right: _index = self._focusable_widgets.index(widget.neighbor_right) @@ -228,7 +238,7 @@ def focus_previous(self): self._focused = len(self._focusable_widgets) - 1 def start_interaction(self): - widget = self._focusable_widgets[self._focused] + widget = self._get_focused_widget() if isinstance(widget, UIInteractiveWidget): widget.dispatch_ui_event( @@ -245,7 +255,7 @@ def start_interaction(self): print("Cannot interact widget") def end_interaction(self): - widget = self._focusable_widgets[self._focused] + widget = self._get_focused_widget() if isinstance(widget, UIInteractiveWidget): if isinstance(self._interacting, UIBaseSlider): @@ -279,12 +289,7 @@ def _do_render(self, surface: Surface, force=False) -> bool: def do_post_render(self, surface: Surface): surface.limit(None) - if len(self._focusable_widgets) < self._focused < 0: - warnings.warn("Focused widget is out of range") - self._focused = 0 - return - - widget = self._focusable_widgets[self._focused] + widget = self._get_focused_widget() arcade.draw_rect_outline( rect=widget.rect, color=arcade.color.WHITE, From 7dc467df209c0deb4379a07fdb68e79058a02059 Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Sat, 8 Mar 2025 10:38:38 +0100 Subject: [PATCH 11/34] Add UIFocusMixin do_post_render None widget handling (#2605) - Adds a None check to do_post_render, so it doesnt crash when trying to access the widget's rect if the widget is None --- arcade/gui/experimental/focus.py | 67 ++++++++++++++++---------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/arcade/gui/experimental/focus.py b/arcade/gui/experimental/focus.py index 5abb775a2..ab0de1bba 100644 --- a/arcade/gui/experimental/focus.py +++ b/arcade/gui/experimental/focus.py @@ -290,39 +290,40 @@ def do_post_render(self, surface: Surface): surface.limit(None) widget = self._get_focused_widget() - arcade.draw_rect_outline( - rect=widget.rect, - color=arcade.color.WHITE, - border_width=2, - ) - - if self._debug: - # debugging - if isinstance(widget, Focusable): - if widget.neighbor_up: - self._draw_indicator( - widget.rect.top_center, - widget.neighbor_up.rect.bottom_center, - color=arcade.color.RED, - ) - if widget.neighbor_down: - self._draw_indicator( - widget.rect.bottom_center, - widget.neighbor_down.rect.top_center, - color=arcade.color.GREEN, - ) - if widget.neighbor_left: - self._draw_indicator( - widget.rect.center_left, - widget.neighbor_left.rect.center_right, - color=arcade.color.BLUE, - ) - if widget.neighbor_right: - self._draw_indicator( - widget.rect.center_right, - widget.neighbor_right.rect.center_left, - color=arcade.color.ORANGE, - ) + if widget: + arcade.draw_rect_outline( + rect=widget.rect, + color=arcade.color.WHITE, + border_width=2, + ) + + if self._debug: + # debugging + if isinstance(widget, Focusable): + if widget.neighbor_up: + self._draw_indicator( + widget.rect.top_center, + widget.neighbor_up.rect.bottom_center, + color=arcade.color.RED, + ) + if widget.neighbor_down: + self._draw_indicator( + widget.rect.bottom_center, + widget.neighbor_down.rect.top_center, + color=arcade.color.GREEN, + ) + if widget.neighbor_left: + self._draw_indicator( + widget.rect.center_left, + widget.neighbor_left.rect.center_right, + color=arcade.color.BLUE, + ) + if widget.neighbor_right: + self._draw_indicator( + widget.rect.center_right, + widget.neighbor_right.rect.center_left, + color=arcade.color.ORANGE, + ) def _draw_indicator(self, start: Vec2, end: Vec2, color=arcade.color.WHITE): arcade.draw_line(start.x, start.y, end.x, end.y, color, 2) From 8db8913ea21902c6a01d2efc7ac6f3f7265aa3cf Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Sat, 8 Mar 2025 21:05:35 +0100 Subject: [PATCH 12/34] adding more workarounds to handle scrollarea setups, set focused on widget --- arcade/gui/experimental/focus.py | 108 ++++++++++++++++++++++--------- 1 file changed, 79 insertions(+), 29 deletions(-) diff --git a/arcade/gui/experimental/focus.py b/arcade/gui/experimental/focus.py index ab0de1bba..1d4e7da52 100644 --- a/arcade/gui/experimental/focus.py +++ b/arcade/gui/experimental/focus.py @@ -5,7 +5,7 @@ from pyglet.math import Vec2 import arcade -from arcade import MOUSE_BUTTON_LEFT +from arcade import LBWH, MOUSE_BUTTON_LEFT from arcade.gui.events import ( UIEvent, UIKeyPressEvent, @@ -57,6 +57,36 @@ def ui(self) -> UIManager | None: w = self.parent return None + def _render_focus(self, surface: Surface): + # this will be properly integrated into widget + self.prepare_render(surface) + arcade.draw_rect_outline( + rect=LBWH(0, 0, self.content_width, self.content_height), + color=arcade.color.WHITE, + border_width=4, + ) + + def _do_render(self, surface: Surface, force=False) -> bool: + rendered = False + + should_render = force or self._requires_render + if should_render and self.visible: + rendered = True + self.do_render_base(surface) + self.do_render(surface) + + if self.focused: + self._render_focus(surface) + + self._requires_render = False + + # only render children if self is visible + if self.visible: + for child in self.children: + rendered |= child._do_render(surface, should_render) + + return rendered + class UIFocusMixin(UIWidget): """A group of widgets that can be focused. @@ -154,6 +184,18 @@ def on_event(self, event: UIEvent) -> Optional[bool]: return EVENT_UNHANDLED + def _ensure_focused_property(self): + # TODO this is a hack, to set the focused property on the focused widget + # this should be properly handled in a property or so + + focused = self._get_focused_widget() + + for widget in self._focusable_widgets: + if widget == focused: + widget.focused = True + else: + widget.focused = False + def _get_focused_widget(self) -> UIWidget | None: if len(self._focusable_widgets) == 0: return None @@ -278,6 +320,8 @@ def end_interaction(self): ) def _do_render(self, surface: Surface, force=False) -> bool: + self._ensure_focused_property() # TODO this is a hack, to set the focused property on the focused widget + # TODO: add a post child render hook to UIWidget rendered = super()._do_render(surface, force) @@ -290,40 +334,46 @@ def do_post_render(self, surface: Surface): surface.limit(None) widget = self._get_focused_widget() - if widget: + if not widget: + return + + if isinstance(widget, Focusable): + # Focusable widgets care about focus themselves + pass + else: arcade.draw_rect_outline( rect=widget.rect, color=arcade.color.WHITE, border_width=2, ) - if self._debug: - # debugging - if isinstance(widget, Focusable): - if widget.neighbor_up: - self._draw_indicator( - widget.rect.top_center, - widget.neighbor_up.rect.bottom_center, - color=arcade.color.RED, - ) - if widget.neighbor_down: - self._draw_indicator( - widget.rect.bottom_center, - widget.neighbor_down.rect.top_center, - color=arcade.color.GREEN, - ) - if widget.neighbor_left: - self._draw_indicator( - widget.rect.center_left, - widget.neighbor_left.rect.center_right, - color=arcade.color.BLUE, - ) - if widget.neighbor_right: - self._draw_indicator( - widget.rect.center_right, - widget.neighbor_right.rect.center_left, - color=arcade.color.ORANGE, - ) + if self._debug: + # debugging + if isinstance(widget, Focusable): + if widget.neighbor_up: + self._draw_indicator( + widget.rect.top_center, + widget.neighbor_up.rect.bottom_center, + color=arcade.color.RED, + ) + if widget.neighbor_down: + self._draw_indicator( + widget.rect.bottom_center, + widget.neighbor_down.rect.top_center, + color=arcade.color.GREEN, + ) + if widget.neighbor_left: + self._draw_indicator( + widget.rect.center_left, + widget.neighbor_left.rect.center_right, + color=arcade.color.BLUE, + ) + if widget.neighbor_right: + self._draw_indicator( + widget.rect.center_right, + widget.neighbor_right.rect.center_left, + color=arcade.color.ORANGE, + ) def _draw_indicator(self, start: Vec2, end: Vec2, color=arcade.color.WHITE): arcade.draw_line(start.x, start.y, end.x, end.y, color, 2) From 3107eaade9f8143fcf0cd12d5d78b57926376fbc Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Sat, 8 Mar 2025 22:52:54 +0100 Subject: [PATCH 13/34] introduce ControllerWindow, UIManager accepts controller input from window --- arcade/gui/experimental/controller.py | 11 +++++---- arcade/gui/ui_manager.py | 33 +++++++++++++++++++++++++++ arcade/gui/view.py | 18 +++++++++++++++ 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/arcade/gui/experimental/controller.py b/arcade/gui/experimental/controller.py index 54be7245d..c81e778b4 100644 --- a/arcade/gui/experimental/controller.py +++ b/arcade/gui/experimental/controller.py @@ -1,14 +1,15 @@ import warnings from dataclasses import dataclass +from typing import TYPE_CHECKING from pyglet.input import Controller from pyglet.math import Vec2 from arcade import ControllerManager -from arcade.gui import ( - UIEvent, - UIManager, -) +from arcade.gui.events import UIEvent + +if TYPE_CHECKING: + from arcade.gui.ui_manager import UIManager @dataclass @@ -117,7 +118,7 @@ class UIControllerBridge(_ControllerListener): that other systems should be aware, when not to act on events (like when the UI is active). """ - def __init__(self, ui: UIManager): + def __init__(self, ui: "UIManager"): self.ui = ui self.cm = ControllerManager() diff --git a/arcade/gui/ui_manager.py b/arcade/gui/ui_manager.py index a55d2013a..5d479f745 100644 --- a/arcade/gui/ui_manager.py +++ b/arcade/gui/ui_manager.py @@ -14,6 +14,7 @@ from typing import Iterable, Optional, TypeVar, Union from pyglet.event import EVENT_HANDLED, EVENT_UNHANDLED, EventDispatcher +from pyglet.input import Controller from typing_extensions import TypeGuard import arcade @@ -31,6 +32,13 @@ UITextMotionEvent, UITextMotionSelectEvent, ) +from arcade.gui.experimental.controller import ( + UIControllerButtonPressEvent, + UIControllerButtonReleaseEvent, + UIControllerDpadEvent, + UIControllerStickEvent, + UIControllerTriggerEvent, +) from arcade.gui.surface import Surface from arcade.gui.widgets import UIWidget from arcade.types import LBWH, AnchorPoint, Point2, Rect @@ -293,6 +301,11 @@ def enable(self) -> None: self.on_text, self.on_text_motion, self.on_text_motion_select, + self.on_stick_motion, + self.on_trigger_motion, + self.on_button_press, + self.on_button_release, + self.on_dpad_motion, ) def disable(self) -> None: @@ -316,6 +329,11 @@ def disable(self) -> None: self.on_text, self.on_text_motion, self.on_text_motion_select, + self.on_stick_motion, + self.on_trigger_motion, + self.on_button_press, + self.on_button_release, + self.on_dpad_motion, ) def on_update(self, time_delta): @@ -452,6 +470,21 @@ def on_resize(self, width, height): self.trigger_render() + def on_stick_motion(self, controller: Controller, name, value): + return self.dispatch_ui_event(UIControllerStickEvent(controller, name, value)) + + def on_trigger_motion(self, controller: Controller, name, value): + return self.dispatch_ui_event(UIControllerTriggerEvent(controller, name, value)) + + def on_button_press(self, controller: Controller, button): + return self.dispatch_ui_event(UIControllerButtonPressEvent(controller, button)) + + def on_button_release(self, controller: Controller, button): + return self.dispatch_ui_event(UIControllerButtonReleaseEvent(controller, button)) + + def on_dpad_motion(self, controller: Controller, value): + return self.dispatch_ui_event(UIControllerDpadEvent(controller, value)) + @property def rect(self) -> Rect: """The rect of the UIManager, which is the window size.""" diff --git a/arcade/gui/view.py b/arcade/gui/view.py index df3cf0164..571439a1e 100644 --- a/arcade/gui/view.py +++ b/arcade/gui/view.py @@ -2,6 +2,8 @@ from typing import TypeVar +from pyglet.input import Controller + from arcade import View from arcade.gui.ui_manager import UIManager from arcade.gui.widgets import UIWidget @@ -58,3 +60,19 @@ def on_draw_before_ui(self): def on_draw_after_ui(self): """Use this method to draw custom elements after the UI elements are drawn.""" pass + + # Controller event mapping + def on_stick_motion(self, controller: Controller, name, value): + pass + + def on_trigger_motion(self, controller: Controller, name, value): + pass + + def on_button_press(self, controller: Controller, button): + pass + + def on_button_release(self, controller: Controller, button): + pass + + def on_dpad_motion(self, controller: Controller, value): + pass From 895c8b7f978a2833db6195e6e59a9c62e888742a Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Sun, 9 Mar 2025 19:11:01 +0100 Subject: [PATCH 14/34] Add missing file --- arcade/experimental/controller_window.py | 93 ++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 arcade/experimental/controller_window.py diff --git a/arcade/experimental/controller_window.py b/arcade/experimental/controller_window.py new file mode 100644 index 000000000..4f1e01788 --- /dev/null +++ b/arcade/experimental/controller_window.py @@ -0,0 +1,93 @@ +import warnings + +from pyglet.input import Controller + +import arcade +from arcade import ControllerManager + + +class _WindowControllerBridge: + """Translates controller events to UIEvents and passes them to the UIManager. + + Controller are automatically connected and disconnected. + + Controller events are consumed by the UIControllerBridge, + if the UIEvent is consumed by the UIManager. + + This implicates, that the UIControllerBridge should be the first listener in the chain and + that other systems should be aware, when not to act on events (like when the UI is active). + """ + + + def __init__(self, window: arcade.Window): + self.window = window + + self.cm = ControllerManager() + self.cm.push_handlers(self) + + # bind to existing controllers + for controller in self.cm.get_controllers(): + self.on_connect(controller) + + def on_connect(self, controller: Controller): + controller.push_handlers(self) + + try: + controller.open() + except Exception as e: + warnings.warn(f"Failed to open controller {controller}: {e}") + + def on_disconnect(self, controller: Controller): + controller.remove_handlers(self) + + try: + controller.close() + except Exception as e: + warnings.warn(f"Failed to close controller {controller}: {e}") + + # Controller event mapping + def on_stick_motion(self, controller: Controller, name, value): + return self.window.dispatch_event("on_stick_motion", controller, name, value) + + def on_trigger_motion(self, controller: Controller, name, value): + return self.window.dispatch_event("on_trigger_motion", controller, name, value) + + def on_button_press(self, controller: Controller, button): + return self.window.dispatch_event("on_button_press", controller, button) + + def on_button_release(self, controller: Controller, button): + return self.window.dispatch_event("on_button_release", controller, button) + + def on_dpad_motion(self, controller: Controller, value): + return self.window.dispatch_event("on_dpad_motion", controller, value) + + +class ControllerWindow(arcade.Window): + """A window that listens to controller events and dispatches them via on_... hooks.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.cb = _WindowControllerBridge(self) + + # Controller event mapping + def on_stick_motion(self, controller: Controller, name, value): + pass + + def on_trigger_motion(self, controller: Controller, name, value): + pass + + def on_button_press(self, controller: Controller, button): + pass + + def on_button_release(self, controller: Controller, button): + pass + + def on_dpad_motion(self, controller: Controller, value): + pass + + +ControllerWindow.register_event_type("on_stick_motion") +ControllerWindow.register_event_type("on_trigger_motion") +ControllerWindow.register_event_type("on_button_press") +ControllerWindow.register_event_type("on_button_release") +ControllerWindow.register_event_type("on_dpad_motion") From adfa0ab58eb24583dd762ab2c1a514b58381595e Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Sun, 9 Mar 2025 20:05:36 +0100 Subject: [PATCH 15/34] Fix a bug where there are less widgets than focus index --- arcade/gui/experimental/focus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arcade/gui/experimental/focus.py b/arcade/gui/experimental/focus.py index 1d4e7da52..6f34f9e90 100644 --- a/arcade/gui/experimental/focus.py +++ b/arcade/gui/experimental/focus.py @@ -200,7 +200,7 @@ def _get_focused_widget(self) -> UIWidget | None: if len(self._focusable_widgets) == 0: return None - if len(self._focusable_widgets) < self._focused < 0: + if len(self._focusable_widgets) <= self._focused < 0: warnings.warn("Focused widget is out of range") self._focused = 0 From bab1b29dfcc2c29ee7bc897e6d3f3903561562c4 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Sun, 9 Mar 2025 21:05:21 +0100 Subject: [PATCH 16/34] fix tests and type hints --- arcade/examples/gui/exp_controller_support.py | 49 +++++++++++------- .../gui/exp_controller_support_grid.py | 13 ++++- arcade/examples/gui/exp_inventory_demo.py | 25 +++++---- arcade/gui/experimental/focus.py | 34 ++++++------ arcade/gui/ui_manager.py | 39 ++++++++++---- .../input_prompt/xbox/xbox_button_a.png | Bin 982 -> 0 bytes .../xbox/xbox_button_a_outline.png | Bin 1269 -> 0 bytes .../input_prompt/xbox/xbox_button_b.png | Bin 900 -> 0 bytes .../xbox/xbox_button_b_outline.png | Bin 1196 -> 0 bytes .../input_prompt/xbox/xbox_button_back.png | Bin 860 -> 0 bytes .../xbox/xbox_button_back_icon.png | Bin 673 -> 0 bytes .../xbox/xbox_button_back_icon_outline.png | Bin 882 -> 0 bytes .../xbox/xbox_button_back_outline.png | Bin 1059 -> 0 bytes .../input_prompt/xbox/xbox_button_color_a.png | Bin 982 -> 0 bytes .../xbox/xbox_button_color_a_outline.png | Bin 1269 -> 0 bytes .../input_prompt/xbox/xbox_button_color_b.png | Bin 900 -> 0 bytes .../xbox/xbox_button_color_b_outline.png | Bin 1191 -> 0 bytes .../input_prompt/xbox/xbox_button_color_x.png | Bin 1036 -> 0 bytes .../xbox/xbox_button_color_x_outline.png | Bin 1336 -> 0 bytes .../input_prompt/xbox/xbox_button_color_y.png | Bin 941 -> 0 bytes .../xbox/xbox_button_color_y_outline.png | Bin 1253 -> 0 bytes .../input_prompt/xbox/xbox_button_menu.png | Bin 774 -> 0 bytes .../xbox/xbox_button_menu_outline.png | Bin 1086 -> 0 bytes .../input_prompt/xbox/xbox_button_share.png | Bin 658 -> 0 bytes .../xbox/xbox_button_share_outline.png | Bin 880 -> 0 bytes .../input_prompt/xbox/xbox_button_start.png | Bin 879 -> 0 bytes .../xbox/xbox_button_start_icon.png | Bin 666 -> 0 bytes .../xbox/xbox_button_start_icon_outline.png | Bin 885 -> 0 bytes .../xbox/xbox_button_start_outline.png | Bin 1077 -> 0 bytes .../input_prompt/xbox/xbox_button_view.png | Bin 846 -> 0 bytes .../xbox/xbox_button_view_outline.png | Bin 1168 -> 0 bytes .../input_prompt/xbox/xbox_button_x.png | Bin 1037 -> 0 bytes .../xbox/xbox_button_x_outline.png | Bin 1336 -> 0 bytes .../input_prompt/xbox/xbox_button_y.png | Bin 941 -> 0 bytes .../xbox/xbox_button_y_outline.png | Bin 1253 -> 0 bytes .../assets/input_prompt/xbox/xbox_dpad.png | Bin 351 -> 0 bytes .../input_prompt/xbox/xbox_dpad_all.png | Bin 351 -> 0 bytes .../input_prompt/xbox/xbox_dpad_down.png | Bin 416 -> 0 bytes .../xbox/xbox_dpad_down_outline.png | Bin 400 -> 0 bytes .../xbox/xbox_dpad_horizontal.png | Bin 435 -> 0 bytes .../xbox/xbox_dpad_horizontal_outline.png | Bin 377 -> 0 bytes .../input_prompt/xbox/xbox_dpad_left.png | Bin 434 -> 0 bytes .../xbox/xbox_dpad_left_outline.png | Bin 389 -> 0 bytes .../input_prompt/xbox/xbox_dpad_none.png | Bin 398 -> 0 bytes .../input_prompt/xbox/xbox_dpad_right.png | Bin 427 -> 0 bytes .../xbox/xbox_dpad_right_outline.png | Bin 391 -> 0 bytes .../input_prompt/xbox/xbox_dpad_round.png | Bin 1032 -> 0 bytes .../input_prompt/xbox/xbox_dpad_round_all.png | Bin 1111 -> 0 bytes .../xbox/xbox_dpad_round_down.png | Bin 1112 -> 0 bytes .../xbox/xbox_dpad_round_horizontal.png | Bin 1118 -> 0 bytes .../xbox/xbox_dpad_round_left.png | Bin 1125 -> 0 bytes .../xbox/xbox_dpad_round_right.png | Bin 1117 -> 0 bytes .../input_prompt/xbox/xbox_dpad_round_up.png | Bin 1128 -> 0 bytes .../xbox/xbox_dpad_round_vertical.png | Bin 1128 -> 0 bytes .../assets/input_prompt/xbox/xbox_dpad_up.png | Bin 436 -> 0 bytes .../xbox/xbox_dpad_up_outline.png | Bin 394 -> 0 bytes .../input_prompt/xbox/xbox_dpad_vertical.png | Bin 422 -> 0 bytes .../xbox/xbox_dpad_vertical_outline.png | Bin 376 -> 0 bytes .../assets/input_prompt/xbox/xbox_guide.png | Bin 1193 -> 0 bytes .../input_prompt/xbox/xbox_guide_outline.png | Bin 1597 -> 0 bytes .../assets/input_prompt/xbox/xbox_lb.png | Bin 589 -> 0 bytes .../input_prompt/xbox/xbox_lb_outline.png | Bin 718 -> 0 bytes .../assets/input_prompt/xbox/xbox_ls.png | Bin 971 -> 0 bytes .../input_prompt/xbox/xbox_ls_outline.png | Bin 1280 -> 0 bytes .../assets/input_prompt/xbox/xbox_lt.png | Bin 533 -> 0 bytes .../input_prompt/xbox/xbox_lt_outline.png | Bin 722 -> 0 bytes .../assets/input_prompt/xbox/xbox_rb.png | Bin 684 -> 0 bytes .../input_prompt/xbox/xbox_rb_outline.png | Bin 800 -> 0 bytes .../assets/input_prompt/xbox/xbox_rs.png | Bin 1065 -> 0 bytes .../input_prompt/xbox/xbox_rs_outline.png | Bin 1354 -> 0 bytes .../assets/input_prompt/xbox/xbox_rt.png | Bin 673 -> 0 bytes .../input_prompt/xbox/xbox_rt_outline.png | Bin 824 -> 0 bytes .../assets/input_prompt/xbox/xbox_stick_l.png | Bin 1228 -> 0 bytes .../input_prompt/xbox/xbox_stick_l_down.png | Bin 1329 -> 0 bytes .../xbox/xbox_stick_l_horizontal.png | Bin 1257 -> 0 bytes .../input_prompt/xbox/xbox_stick_l_left.png | Bin 1263 -> 0 bytes .../input_prompt/xbox/xbox_stick_l_press.png | Bin 1309 -> 0 bytes .../input_prompt/xbox/xbox_stick_l_right.png | Bin 1248 -> 0 bytes .../input_prompt/xbox/xbox_stick_l_up.png | Bin 1319 -> 0 bytes .../xbox/xbox_stick_l_vertical.png | Bin 1405 -> 0 bytes .../assets/input_prompt/xbox/xbox_stick_r.png | Bin 1286 -> 0 bytes .../input_prompt/xbox/xbox_stick_r_down.png | Bin 1384 -> 0 bytes .../xbox/xbox_stick_r_horizontal.png | Bin 1324 -> 0 bytes .../input_prompt/xbox/xbox_stick_r_left.png | Bin 1317 -> 0 bytes .../input_prompt/xbox/xbox_stick_r_press.png | Bin 1364 -> 0 bytes .../input_prompt/xbox/xbox_stick_r_right.png | Bin 1321 -> 0 bytes .../input_prompt/xbox/xbox_stick_r_up.png | Bin 1370 -> 0 bytes .../xbox/xbox_stick_r_vertical.png | Bin 1465 -> 0 bytes .../input_prompt/xbox/xbox_stick_side_l.png | Bin 565 -> 0 bytes .../input_prompt/xbox/xbox_stick_side_r.png | Bin 654 -> 0 bytes .../input_prompt/xbox/xbox_stick_top_l.png | Bin 1268 -> 0 bytes .../input_prompt/xbox/xbox_stick_top_r.png | Bin 1359 -> 0 bytes tests/unit/resources/test_list_resources.py | 4 +- 93 files changed, 107 insertions(+), 57 deletions(-) delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_a.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_a_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_b.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_b_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_back.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_back_icon.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_back_icon_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_back_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_a.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_a_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_b.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_b_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_x.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_x_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_y.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_color_y_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_menu.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_menu_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_share.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_share_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_start.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_start_icon.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_start_icon_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_start_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_view.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_view_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_x.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_x_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_y.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_button_y_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_all.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_down.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_down_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_horizontal.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_horizontal_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_left.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_left_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_none.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_right.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_right_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_all.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_down.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_horizontal.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_left.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_right.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_up.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_vertical.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_up.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_up_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_vertical.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_dpad_vertical_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_guide.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_guide_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_lb.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_lb_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_ls.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_ls_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_lt.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_lt_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_rb.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_rb_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_rs.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_rs_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_rt.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_rt_outline.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l_down.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l_horizontal.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l_left.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l_press.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l_right.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l_up.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_l_vertical.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r_down.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r_horizontal.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r_left.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r_press.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r_right.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r_up.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_r_vertical.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_side_l.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_side_r.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_top_l.png delete mode 100755 arcade/resources/assets/input_prompt/xbox/xbox_stick_top_r.png diff --git a/arcade/examples/gui/exp_controller_support.py b/arcade/examples/gui/exp_controller_support.py index d99cf2cf9..07fc8a9af 100644 --- a/arcade/examples/gui/exp_controller_support.py +++ b/arcade/examples/gui/exp_controller_support.py @@ -1,3 +1,14 @@ +""" +Example demonstrating controller support in an Arcade GUI. + +This example shows how to integrate controller input with the Arcade GUI framework. +It includes a controller indicator widget that displays the last controller input, +and a modal dialog that can be navigated using a controller. + +If Arcade and Python are properly installed, you can run this example with: +python -m arcade.examples.gui.exp_controller_support +""" + from typing import Optional import arcade @@ -34,7 +45,7 @@ class ControllerIndicator(UIAnchorLayout): """ BLANK_TEX = Texture.create_empty("empty", (40, 40), arcade.color.TRANSPARENT_BLACK) - TEXTURE_CACHE = {} + TEXTURE_CACHE: dict[str, Texture] = {} def __init__(self): super().__init__() @@ -54,39 +65,39 @@ def input_prompts(cls, event: UIControllerEvent) -> Texture | None: if isinstance(event, UIControllerButtonEvent): match event.button: case "a": - return cls.get_texture(":resources:input_prompt/xbox/xbox_button_a.png") + return cls.get_texture(":resources:input_prompt/xbox/button_a.png") case "b": - return cls.get_texture(":resources:input_prompt/xbox/xbox_button_b.png") + return cls.get_texture(":resources:input_prompt/xbox/button_b.png") case "x": - return cls.get_texture(":resources:input_prompt/xbox/xbox_button_x.png") + return cls.get_texture(":resources:input_prompt/xbox/button_x.png") case "y": - return cls.get_texture(":resources:input_prompt/xbox/xbox_button_y.png") + return cls.get_texture(":resources:input_prompt/xbox/button_y.png") case "rightshoulder": - return cls.get_texture(":resources:input_prompt/xbox/xbox_rb.png") + return cls.get_texture(":resources:input_prompt/xbox/rb.png") case "leftshoulder": - return cls.get_texture(":resources:input_prompt/xbox/xbox_lb.png") + return cls.get_texture(":resources:input_prompt/xbox/lb.png") case "start": - return cls.get_texture(":resources:input_prompt/xbox/xbox_button_start.png") + return cls.get_texture(":resources:input_prompt/xbox/button_start.png") case "back": - return cls.get_texture(":resources:input_prompt/xbox/xbox_button_back.png") + return cls.get_texture(":resources:input_prompt/xbox/button_back.png") if isinstance(event, UIControllerTriggerEvent): match event.name: case "lefttrigger": - return cls.get_texture(":resources:input_prompt/xbox/xbox_lt.png") + return cls.get_texture(":resources:input_prompt/xbox/lt.png") case "righttrigger": - return cls.get_texture(":resources:input_prompt/xbox/xbox_rt.png") + return cls.get_texture(":resources:input_prompt/xbox/rt.png") if isinstance(event, UIControllerDpadEvent): match event.vector: case (1, 0): - return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_right.png") + return cls.get_texture(":resources:input_prompt/xbox/dpad_right.png") case (-1, 0): - return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_left.png") + return cls.get_texture(":resources:input_prompt/xbox/dpad_left.png") case (0, 1): - return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_up.png") + return cls.get_texture(":resources:input_prompt/xbox/dpad_up.png") case (0, -1): - return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_down.png") + return cls.get_texture(":resources:input_prompt/xbox/dpad_down.png") if isinstance(event, UIControllerStickEvent) and event.vector.length() > 0.2: stick = "l" if event.name == "leftstick" else "r" @@ -94,13 +105,13 @@ def input_prompts(cls, event: UIControllerEvent) -> Texture | None: # map atan2(y, x) to direction string (up, down, left, right) heading = event.vector.heading() if 0.785 > heading > -0.785: - return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_right.png") + return cls.get_texture(f":resources:input_prompt/xbox/stick_{stick}_right.png") elif 0.785 < heading < 2.356: - return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_up.png") + return cls.get_texture(f":resources:input_prompt/xbox/stick_{stick}_up.png") elif heading > 2.356 or heading < -2.356: - return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_left.png") + return cls.get_texture(f":resources:input_prompt/xbox/stick_{stick}_left.png") elif -2.356 < heading < -0.785: - return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_down.png") + return cls.get_texture(f":resources:input_prompt/xbox/stick_{stick}_down.png") return None diff --git a/arcade/examples/gui/exp_controller_support_grid.py b/arcade/examples/gui/exp_controller_support_grid.py index 5cddcae31..b7173967a 100644 --- a/arcade/examples/gui/exp_controller_support_grid.py +++ b/arcade/examples/gui/exp_controller_support_grid.py @@ -1,3 +1,13 @@ +""" +Example demonstrating a grid layout with focusable buttons in an Arcade GUI. + +This example shows how to create a grid layout with buttons that can be navigated using a controller. +It includes a focus transition setup to allow smooth navigation between buttons in the grid. + +If Arcade and Python are properly installed, you can run this example with: +python -m arcade.examples.gui.exp_controller_support_grid +""" + from typing import Dict, Tuple import arcade @@ -6,6 +16,7 @@ UIFlatButton, UIGridLayout, UIView, + UIWidget, ) from arcade.gui.experimental.controller import ( UIControllerBridge, @@ -17,7 +28,7 @@ class FocusableButton(Focusable, UIFlatButton): pass -def setup_grid_focus_transition(grid: Dict[Tuple[int, int], Focusable]): +def setup_grid_focus_transition(grid: Dict[Tuple[int, int], UIWidget]): """Setup focus transition in grid. Connect focus transition between `Focusable` in grid. diff --git a/arcade/examples/gui/exp_inventory_demo.py b/arcade/examples/gui/exp_inventory_demo.py index c91fad1dd..86faf0ff9 100644 --- a/arcade/examples/gui/exp_inventory_demo.py +++ b/arcade/examples/gui/exp_inventory_demo.py @@ -10,8 +10,11 @@ - Move items between slots - Controller support +If Arcade and Python are properly installed, you can run this example with: +python -m arcade.examples.gui.exp_inventory_demo """ +from functools import partial # TODO: Drag and Drop from typing import List @@ -204,7 +207,7 @@ def __init__(self, inventory: Inventory, **kwargs): # fill left to right, bottom to top (6x5 grid) self.add(slot, column=i % 6, row=i // 6) self.grid[(i % 6, i // 6)] = slot - slot.on_click = self._on_slot_click + slot.on_click = self._on_slot_click # type: ignore InventoryUI.register_event_type("on_slot_clicked") @@ -235,13 +238,13 @@ def __init__(self, **kwargs): equipment = Equipment() self.head_slot = self.add(EquipmentSlotUI(equipment, 0)) - self.head_slot.on_click = lambda _: self.dispatch_event("on_slot_clicked", self.head_slot) + self.head_slot.on_click = partial(self.dispatch_event, "on_slot_clicked", self.head_slot) self.chest_slot = self.add(EquipmentSlotUI(equipment, 1)) - self.chest_slot.on_click = lambda _: self.dispatch_event("on_slot_clicked", self.chest_slot) + self.chest_slot.on_click = partial(self.dispatch_event, "on_slot_clicked", self.chest_slot) self.legs_slot = self.add(EquipmentSlotUI(equipment, 2)) - self.legs_slot.on_click = lambda _: self.dispatch_event("on_slot_clicked", self.legs_slot) + self.legs_slot.on_click = partial(self.dispatch_event, "on_slot_clicked", self.legs_slot) EquipmentUI.register_event_type("on_slot_clicked") @@ -251,7 +254,7 @@ class ActiveSlotTrackerMixin(UIWidget): Mixin class to track the active slot. """ - active_slot = Property(None) + active_slot = Property[InventorySlotUI | None](None) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -307,14 +310,16 @@ def __init__(self, inventory: Inventory, **kwargs): self.add(content, anchor_y="bottom") inv_ui = content.add(InventoryUI(inventory)) - inv_ui.on_slot_clicked = self.on_slot_clicked + inv_ui.on_slot_clicked = self.on_slot_clicked # type: ignore eq_ui = content.add(EquipmentUI()) - eq_ui.on_slot_clicked = self.on_slot_clicked + eq_ui.on_slot_clicked = self.on_slot_clicked # type: ignore # prepare focusable widgets widget_grid = inv_ui.grid - setup_grid_focus_transition(widget_grid) # setup default transitions in a grid + setup_grid_focus_transition( + widget_grid # type: ignore + ) # setup default transitions in a grid # add transitions to equipment slots cols = max(x for x, y in widget_grid.keys()) @@ -343,7 +348,7 @@ def __init__(self, inventory: Inventory, **kwargs): anchor_x="right", anchor_y="top", ) - close_button.on_click = lambda _: self.close() + close_button.on_click = lambda _: self.close() # type: ignore def close(self): self.trigger_full_render() @@ -379,6 +384,8 @@ def on_key_press(self, symbol: int, modifiers: int) -> bool | None: print(i, item.symbol if item else "-") return True + return super().on_key_press(symbol, modifiers) + def on_draw_before_ui(self): pass diff --git a/arcade/gui/experimental/focus.py b/arcade/gui/experimental/focus.py index 6f34f9e90..fe5ce5891 100644 --- a/arcade/gui/experimental/focus.py +++ b/arcade/gui/experimental/focus.py @@ -50,11 +50,13 @@ class Focusable(UIWidget): @property def ui(self) -> UIManager | None: """The UIManager this widget is attached to.""" - w = self - while w.parent: - if isinstance(w.parent, UIManager): - return w.parent - w = self.parent + w: UIWidget | None = self + while w and w.parent: + parent = w.parent + if isinstance(parent, UIManager): + return parent + + w = parent return None def _render_focus(self, surface: Surface): @@ -191,10 +193,11 @@ def _ensure_focused_property(self): focused = self._get_focused_widget() for widget in self._focusable_widgets: - if widget == focused: - widget.focused = True - else: - widget.focused = False + if isinstance(widget, Focusable): + if widget == focused: + widget.focused = True + else: + widget.focused = False def _get_focused_widget(self) -> UIWidget | None: if len(self._focusable_widgets) == 0: @@ -215,7 +218,7 @@ def _walk_widgets(cls, root: UIWidget): yield child yield from cls._walk_widgets(child) - def detect_focusable_widgets(self, root: UIWidget = None): + def detect_focusable_widgets(self, root: UIWidget | None = None): """Automatically detect focusable widgets.""" if root is None: root = self @@ -286,8 +289,8 @@ def start_interaction(self): widget.dispatch_ui_event( UIMousePressEvent( source=self, - x=widget.rect.center_x, - y=widget.rect.center_y, + x=int(widget.rect.center_x), + y=int(widget.rect.center_y), button=MOUSE_BUTTON_LEFT, modifiers=0, ) @@ -312,15 +315,16 @@ def end_interaction(self): widget.dispatch_ui_event( UIMouseReleaseEvent( source=self, - x=x, - y=y, + x=int(x), + y=int(y), button=MOUSE_BUTTON_LEFT, modifiers=0, ) ) def _do_render(self, surface: Surface, force=False) -> bool: - self._ensure_focused_property() # TODO this is a hack, to set the focused property on the focused widget + # TODO this is a hack, to set the focused property on the focused widget + self._ensure_focused_property() # TODO: add a post child render hook to UIWidget rendered = super()._do_render(surface, force) diff --git a/arcade/gui/ui_manager.py b/arcade/gui/ui_manager.py index 5d479f745..c1cda8f98 100644 --- a/arcade/gui/ui_manager.py +++ b/arcade/gui/ui_manager.py @@ -18,6 +18,7 @@ from typing_extensions import TypeGuard import arcade +from arcade.experimental.controller_window import ControllerWindow from arcade.gui import UIEvent from arcade.gui.events import ( UIKeyPressEvent, @@ -41,7 +42,7 @@ ) from arcade.gui.surface import Surface from arcade.gui.widgets import UIWidget -from arcade.types import LBWH, AnchorPoint, Point2, Rect +from arcade.types import AnchorPoint, LBWH, Point2, Rect W = TypeVar("W", bound=UIWidget) @@ -288,6 +289,18 @@ def enable(self) -> None: """ if not self._enabled: self._enabled = True + + if isinstance(self.window, ControllerWindow): + controller_handlers = { + self.on_stick_motion, + self.on_trigger_motion, + self.on_button_press, + self.on_button_release, + self.on_dpad_motion, + } + else: + controller_handlers = set() + self.window.push_handlers( self.on_resize, self.on_update, @@ -301,11 +314,7 @@ def enable(self) -> None: self.on_text, self.on_text_motion, self.on_text_motion_select, - self.on_stick_motion, - self.on_trigger_motion, - self.on_button_press, - self.on_button_release, - self.on_dpad_motion, + *controller_handlers, ) def disable(self) -> None: @@ -316,6 +325,18 @@ def disable(self) -> None: """ if self._enabled: self._enabled = False + + if isinstance(self.window, ControllerWindow): + controller_handlers = { + self.on_stick_motion, + self.on_trigger_motion, + self.on_button_press, + self.on_button_release, + self.on_dpad_motion, + } + else: + controller_handlers = set() + self.window.remove_handlers( self.on_resize, self.on_update, @@ -329,11 +350,7 @@ def disable(self) -> None: self.on_text, self.on_text_motion, self.on_text_motion_select, - self.on_stick_motion, - self.on_trigger_motion, - self.on_button_press, - self.on_button_release, - self.on_dpad_motion, + *controller_handlers, ) def on_update(self, time_delta): diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_a.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_a.png deleted file mode 100755 index 2399fc263be2c40edfe062170cfdfa21370876f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 982 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDvQL(vI4BQEfIt{EJ z<}<0(?~<`o-hFwwJo5#;_@`>?-h8QHn4i4T<$`Df)xzmE{8WV?BZxwW1M2r z@Q5KskpDP?M_nt}kIpyN-x121< zXJYYc`h8uy_P#*{W5*O0dGVVb=jU%{n8I>5IqLkn!W--p zw%sY*tY9v!#4hZwbEN#!sm1P_Up>51zrWN~EcHb})bEyv-A5R|wcYr5hvE3d-S$lG zB5#@&O==GL-TzIHbDU{V(; zo3=&18x!Pm*B=4pucw7gRP zkGIZX(ODD(ubJ3l<|GL__D_qfYIHT9MZ(r3| zU>hbAjI6}Xk}&H8&N i36$P9Fn|;Peum_{^Xu$%m(2j?ECx?kKbLh*2~7aTJiwm- diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_a_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_a_outline.png deleted file mode 100755 index 8dd7cb9f77034b57f8b1695c3b178f4173163f1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1269 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDpk*#bgXQrNYTiBtd#Qw#SOFeRS067-Sd4VT>iPc)1!K4mZ2BK~ z_NuR|2Rln|{lU|JD||Z|m<75`V@tl4ZHzDEO_+J`>PK^?Jl4+)-)!r6K0g(kX7`4b z;r^!o$3wq-WjM`v#pEBq<4cD{hxm`)x#5ziWZAI2rlk7L-)}t5vJFq_L$9)x33qI6 z5M#EO>2yq}qnI)4-6fIdB9j;vrdUsXn|qOwtsqwCLrM@IE5{7=_i3$!F?FGTzUWfZ6@i>l-KPSUhgL92DDtj)`{=oYlCZ3azL^(e_5#%|^ z<(R2q8{T?t!4`pOuZ0@&_)e!@6-48JxQH5EpdfsfPv<$uU3b0ii6rhHD^7|J{{{m zBPPPh&dgqY)yYg30nZ zd1uvbC)ZdPT6?WIUnpy?@OWv*@^>GS7W}w+W@qfh#~0uAFHrvf%eD2!=C4=NDtDb> zn;A0c+y#{*Pd}RJr)#Z?%Y z#$2lOV9qeweV3!{i(=u0?Mps?-x(Hug=_v?(bT=i79alIm9cHrhRXPLGxJN2UY)x1 z?liW#FO6E~-~4*?Z9$A$u=w0n{14Vm{chj#MZW7pTvvAB;lKRd(ht~nFy=LYsXE5Q X2Rm5AnP+eU3myhfS3j3^P6zanPW!H8vaQ5mNGKOV0Ol7}t z;f@shfln+veoh~%m{#;3if-u86FbT9fSDnr;ULo<-5Fkat1~#6{&Upm-QM*;VGpxK z3xo5QW;TPWR#C|mHO2;}gb&FIj51OUyNtg-5M8igV5w~hA9kxPWm0mfR|BB^MDsqL>oh_ zRLe|}mRgPtry2ImR(!ap*o4L5%bt$7`dq$%4@nJmh77+|H?lRXW_(pw%99h$+@mM7 zeD((CC9R3ycXv&`7|L_$_=?G9g8%+LxA@JJvVY1N>7^-`mVBA`DSL8IWK`ek*r;{7 zKD)LV1WrryjGgte>+)w-1<~cZKJAg5@_ZKC(X@*T0=8rvSmJ8@>&){X(xRpht}olY z+^I4qIPi8!{2EW&yJAJlO@1<{RBhf>{o{R4-cIlBRz+-L=GFhruN_(P?%Kcar)Ep0 zW;dLXy?wVXmD!IW$2@WUpOcxJWbUe+{9yH){XjxZzL`x<5M%Sj4_r@#_zJ$;3vD#H z|5g1&#TBDT6Q^Wqq};0c8@wc@OFGkhLebs#E<0!RDlAui$$3!uPh`_EB|(!*U$&-r zs{j6JJhdcf(!b{{Gjs~mPE>JQhQ8$~HTJ#{<8kqXaJcc(yQVzno=DA5D&M!mHt&OC zsAWR;oAW21{MFq4PegF<_Bk%|XZ=q!L`tUi(jP8(+s@hm%0>*Hu6{1-oD!M<+d`uD diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_b_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_b_outline.png deleted file mode 100755 index 474d894f8b2976f5fd2eb083e9e9ea1c98086706..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1196 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD+5n zO^X!-9HIps9lreEUuI;U<&&hZvvcOkrt2$Y7L;3UsHt>d&S2nOz-ZFIc7fqvFoXDk zA3r7TITY+?GmwlJmd9c{=lq_LT14wbdCtnyeOPB|b)l4=1q3vpu*x{r@h`eJly~ z|5r?ZvT(5npMyz#+w|R)zJiRb3(9g&m3+;+9zT_R!+M4Cf9#A~n=Kk{KkF|$DOdKt z?KHy-?{oi4#h;fn++`3wTd(|pN60{b)zSiuG)0L6YIe1iclK7c>G2-8kgwX?XT!3< zWPuCgy%5HWEDK^9;#TqaTDE#KoPO!|=!VIyF2)*B-kM-debxz#Yh+F?ii$)}8Kn1y2R)blbi9F|m6Gn}W9oWjEJUA>XL;+9!y%fk+4)|A+W zntCs03)cn4E7@%rzA3O5xX5Y$ymf>r;~IaWupz_etwjg7@_hTs_$ynphFQWw@`&0j z35Fflf_B_nIf17jW$}%dt*_(nGaR#N$V|CXz$_8T5XNc5EBNi{D~8XXQX4*`GHkAq zbf}kZIFVZC$yOuFFnfL5w7wqu6wYQjOT#HqhjN1Ngc=>SyDrH((fHH&~plEs5 z=i(gp1P|3IOP_arbhq4hVVQ^qmzT*dugB;ON>u|ebI)zOQ%X^ zpIaBIb#%EX?<235D!YqC_*Z@63Mn+dGSfc5k7yB>2j0xmkwb^fqr@}ia6 zcO){Z%ni4%&WgG9m!o7!{{Ddd_ov54|MLh=$=lx`Ja?Uk!1n}M@$`i6>slNNDt%hw zsvqW`nby2G&p9Gfi`g)HrivUdONcTjhslmDjg4E>Hgs?ptt+U$_$NlVTQff?QL54* z?f8+kiMtv4FNLXXo9H>+)M(?jm{}drQ$=FaG-0iCfQnyHcsKF>z6y zlRnSiymWgP&%Iir53WdkyKLEQuwr>%bmHfkJC=T4s&vQMVs0Dnh4bhAujIb0Ikn%_ zcWu(`nHR1}r@j1A>|`?g%8i=5>nfiPFG;Z4xb?Q<@hR*I7t@xTZ;!6Md;8z^-LcEr y3o76Koow{^&z(bwA#qnO{7byZAO+9U_H(}PN?DjPV=u70VDNPHb6Mw<&;$VL@Fx@i diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_back.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_back.png deleted file mode 100755 index 3318c6a5adfc5f8eaaa6d57f997d43d3fda034ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 860 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDEaktaqI1@ zmy?>TJ0Uu zH+ekgo{-FR!LxwTL~XK39m7tU2W5@S4l{EW?O`afV|dGaz}M}e9K-TI!G8W%w?@SV zfBUNNKf83j_qpYFc0MUBJr!ch;AgzySb)abR@e1&eqGov8@=J}SD85se&=UA=KQ#S z+QF6m^}5{W7TjlJeWB0rjN_uL)3dbV!pVQ8Lz+`YB*?efV*Te$UV->ttR z?RA&QgC*g9mi62jeU=Ha`W6nIj8pU`JSCcI~U~@{f;UL2`%>xWxOHYNe>#XKu zDbU#zT>WbOWac;9Zt6Dt-m1j4z?Qd=b-^X(Gs(Z09K0F~H|gs+Fs{mayFO6-4pT^6 z;Q`hQx#F4qI~H*9yKFCH*fvq1xRHnNXoXC}&j}gd7D^O0dKE~>GX7AUIn&-f=7PKI z@pl#hvSJsGB?}bD%zu16DoJqa&0~L-#$J|*zOZqA_Vsy}6g6kAE$Lhux0K<^wymxb zFV6n^&pPLL(*6lk>TGrYZ>V87!|m{dS)fjT`5RYu3&tbed(?9>+>-x$|nMK}Md@hcCDm`0GWX`wlWG0|lXkZ_kz~!L-O>7@` zWO!Z_5hzwK{36rKU^v;xqD3(wT6*Q;wYMW3>?ZzmJh7Z%5X%P@om-u+ya7a6`@$07L1{qlQS4;UtC+4DPeGQL># zXZIiB_YA&M=W}KxJ6w30&ZNP^7&(1D_X+0%t4^P1UE}N!x=t;x*M`OC)YUUAKF$qB z_Tmm|4T~rLUEEM$su0H;v5rkfG(4>#QZwP%{IzC_9&HmW-2Uj%R?*G;iyxIpeC&Vx zs91)ZYto~j2_9aX9tBPE?7H+Qa7x#yPmcnoyE-u=7ZogOb*1P{X zWlUE8cK`JM`a1JBV$gTe~DWM4fZEH8w diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_back_icon_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_back_icon_outline.png deleted file mode 100755 index a9ee088896fda59792127c27a7c3ec7e4ed52c5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 882 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDrIOlcp4mC^_V{W^WR;)=So@s1eO9T>&buIKABFCUKz1pr-@Ob0So#k&5-N%Uzu}4 z?!}Cfa0c6UmuUvC{TQnJrZzb;FwW^(8MUZrEkkkhFW$sFrVDna-PgY|JbU}xef_kT z>mAhoMjEd!|B#WzI4xUv>B@h(N56gzVmFABOU-ub*;`Y-hOxqO{`Reb=l1y}En}YZ zt>4>wL9OLQCsvEE-nYeGsdp$aC-lGH6D{q^->$05dZ6@j{aaC4#s>up7dHPpJ&TFI zAcownh`35J9YnGvD+8?HsWSF6CwZS3cv90-w%}D%`I5+s-5vzwY$jo1#PS3*$lof`^&1Z zy;L~Vo@c{xB~GRV+kUT=FU)>_J(O|Am(+%bw;4X&W_bLWfp7Bd3n8L`!<)GfgdF`*FXjMPY%{1pH;pN$V@vJTmWyg0t z|9oUy;+gX3MHBLz^&1uztXTBLb-8#`K-L1+l~aAZ-T%(CRy~!P{#$d$<_423?WZ13 zRpPwvxnF7DlsLgCmwh70+J z*PUuS`uv33D%M)*H;YpGC!YO%WMN`P=rgwCWov)kK34Yn_><*M4>E-xS>~;B;}G6s zT~rZUC|Pul}e>S|B^ z*wZ`5qduEWZSMXn!MXljJ zeAAfD%%gjMzhDoiaYGB!vM|T?tt%D?DZFcp*uA0s_+LR6)(cyC_N0WcFfzP8@_ZVD ziq;i|2Em35>FQb4j?)=@*rsh`RWRJ$#<992ufpJ>UikIU-R=9lE9{uxyi$G;mn<+* z+r{L;oSW}@bk@YryS{sV#To1D1HaNv1iP@yF__M_{8q&(Hn-^Rk;JR#f4*P-_=WEI z+zhb>d(#IDGn(H#;ZQQ!u78bx_4J1a7!I{>sAtG2`d4cpXC?Gmhp*G_d75qgiHp0; zr?F0$w^eRY$^6^rv`oLVCiFU_F}j`SzS--^aiq2{%<1k&XvT_ zGCL$^es=twf9LcWnB1P(?)|#T^1#~}sf;%+$tjm|8A z3|{BIo)T}=VtJ8t{KGdMMvm(qGu4|cWu8s3UbII3!aDUYt^uz)lGa(gW8V^a|9TSt;FLJGm9H({u)o;aV5hQduSIhhi;8>I&c2eu+J7JS2;DsS zH|B2cJ`077*LQz-6{5T%IWOqj&!~r zk$1_|lzHASy9t*frJE9cm+qcubhpI&Skwvow96ZvRI^0NgD6re{`Eo6NRJ-xl z8OzA8g?nzPLYsDUuUC00004XF*Lt006O% z3;baP0000pP)t-se6|36wg7y#0DZUsez*X8w*Y*&0DH9neYOC6w*Y*%0DZOqce?<6 zw*Y;&00000eYXJQXy*X{000nlQchFPkFPIJUvD4pf4@Hu-=6?3wDXMs00S~fL_t(| z+U=X$a@-&gM3F#3T+RP~ZJesu-p$I;4TDpuVqR^DBc{1duX_8(p%%abSO5!P0W5$8 z@Sg(2aJuY2BfB%i9|O=XU*#sQ?DTwqkW;Bd%3&seb=B7YFgQC2!0rW%-A(|&o97Tf zfVf?SiffCK045bWk`V)lxpYO2G5}VU&QMkY;3xxej03nb6mAqCm%+$m0K@G!3Dms& zV>(dN+iwum089}I(+8k0DS%7&07S9&-w9#|fG>jKc>q^L!?6Im2!|#Clr(_i09+9d z#{fuEnSk&;@E>2%H}eoC5>l4gehjpR$hV zK)?VDpHfsb1_D6e_HYgk{W4VD8Ay-Mr0yKN0t8lqj0^(+;8+PVGIRid?@A{q91j5W zbtam)Hh|_NXe}p0-2^x|32qxF!=nk{I0;@CCqvf+*eMAfU*CkuaBl*5N`m{>D?{Hf zcmYV11RodKM9-hM0F(sXV_MIyao7hCA3r)#G86&G&kMI>L9Rd*6HlmK}7U`B=l0QoiL0hgh`OjxOj1c8~*({mX$kG26aD#6=0(fkc8$JIOd zrVQK5v0*=f>p(NSC2N%BS~Aq5o6^*}eov#D(#9}2TL9EAU7K#2a`Z7#CP(_ zqervq;WT`|bww6s=x|#5gjBo40rrO2@!SB%k-I+vM-=RWqLPDH`;Yb!1-zp9J_ose zd^^zRyRYd&eX8&+8KT#8Z&aloj~%`#)pGzeep&5jwC{=A@+r5~mMK_l^6hU_#Uv)_ z_EY3)Mn)RIuw}dLM-os0kWE60eC1S2mTq-V@!HH6K%;ywRy=gQ1_q(xRqnTN1u0W5$8umBdo0$2dQ0N+{9u5x+{LI3~&07*qoM6N<$ Eg4fEZApigX diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_color_a_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_color_a_outline.png deleted file mode 100755 index 9699da0f5f9c258ba96df29944d9edec328a9a15..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1269 zcmVC00004XF*Lt006O% z3;baP0000pP)t-se7FF7wg7y#0Diate7697w*Y;(0DZOqd$$06wg7j#0DZRrd$j<3 zw*Y;%00000eYXI}RPD3?000nlQchEE&#zB^-!G3}KM$WDzwZE9ct};ei49_7NIRV|&kihL3kjjn{Er*m05`x5a0A=`H^2?> zp8{Az(uK7-Nyh#)fQNMPnP}0;$M*q@o1RRYocShzm+a_o2I;>9;KwJ}M*jr>eE2#9 z5WuQ~?xyia_-i9M9yTW`+Y>;1bU&!1(u^8D5Jfit>-PN;Gc`+lQfK=B_}#?HR%Ujz zgYN;Lw+b#z_R~Q&(%#w zNCyOsf5FTBcuk0a0}zWfU3B$!v0rfwV41r?@>;-_(vj4|b z4D|rZ0n{}94?*cbqQVcw+}K26A~}G!d{lef{q_t)9f0J2Rn-P|6Ns~b76Uk$LdFt^ za{+qp(lqB3k^sJBJC*Hi(H;hHT`cfqQVs?1m?vMp%S$eyc|qa9FfWA-xSR+Vxqt6>R3deON(I0Kkhmll>FBt!f9 z&Hw-sT-1--Y6Le-0syZssX~I#j!|Y_=Yc>Q2%i7}w1YB0?!$}(rwNr|W@40H+JH)s z2$f+-Zxy9As89)7i^@=?Rd@mzAr0#7;^#mazLsh%0)&jcrX?u0sI#J3W^5hW=$qGTKFFjZJ)}XWyma7tgY7Wa~I&>-> z`=!%xgfAxS)$HJi&m-hySaCIDFQ*}&zvP{A#pU#U6oq0en2shhG7J^hbCK_7y^iE% zmX@KfNl-8;n)N!uY;#hERVkX~Ys+0nm|R#wJ~lrms>x|B5;UB@OUXyprRf%n>XIqm z&1i%(r4KMui8e0tN}RcyKI0qifFN$oCfj3tJ8?Totj}>BHz4Dsh^OxGVxale%(-3B zRr1ST=R#(&tpPdzDy(XZ;hl*&ns<3%q zu9i9Yf;1f4~rY%2Dkxk ffE(ZjxB-3vC00004XF*Lt006O% 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 R8Zad?c)I$ztaD0e0stH4WxW6Z diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_menu_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_menu_outline.png deleted file mode 100755 index 1848da3400b7f7ba2ee9c5ceb90721bd37a205b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1086 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD#8t!5Ke3p%O0i#I++XV(G2j(CC zjB{9@o;|&X>GeOU150Wml*IPTTF1U2POrl=e?7|uF4@}DfL}@sclBLfe`RI(^6<1k z2SeVE57syBW&Wow_|<=wvw1bA0^j=gZ7Y`FTxhM(mBn!G{eA8eS7hFJ?p@8ipj`O0 z`^>u+%XYCH_)+)Jp-9(uPrczM#w)ispFDYOud-VqV;r|uchH>LpH0she|&mbuh;aP zA?LUJv7I-LIecPP=&gS=rFzaR6($~soQox&R+LBo=_+Qp&2z5rzuJK?1KtPMJ~q$u zpMCG%Lq7(IbN}ksEz)GaAtaFfhuu+6(eYV(QrfH&J}RsmcAY=4e)sjs3$>X8eys0W z<802jf#U(ofp85KCB_ZP33FE8=#b?KWSHAM*C|>#YBFQZRE|Gfn*OX8K0fz8_31~C ziJ-$#`E-UCB??CvoN5kAFuWFGk!tX$U|>)1dKT)j^FcY^BZf6MPfTWTQexl0aO{d) z4fBor;X8IQy!-xs8pDDh&koiNYRaw*KWaBsedZGS+;qfc`7MUvq|An23b%!Xn|JJ< z^@~AGE=J({!G(4Q1il~AZ}=xAe`n8z^Gq{b_G~(P?E5!vhd6G9%?vLU9;hz5GF#e# zIfH?dAy(c&m@)5s%Xa1;+5F1?-^(9=bgRilz0rF z&imHv`E0%Y##{cnHvTo790rP<0!lNq6C5^ko>6HMj`w2D{4=M+^`onfq$2-?PZE(* z+blOM@tV0wUP0Oa#FVp7Sv03FIlaj4Tkb^BB{9~9XD&-_o*DUmO8XQUw_j{`IG;H6 z|7v9K0bf{`}qu$E=@ejz`*#<)5S5Q;?~<+ zw>LEz@URAmyx6<(|NpJWPcKfGtY+J1Y`vZ-GAP>XQPJ-r9iUNY;J|r?q#LJ{qtxfI z=PJ%(N?NAIcj_XCK(RCr-$L#ivAKr>@2+D_as8#PxSC<3N$C^|K8~%DF1(8yuJkDN zGHy!~yUn3se)vR@L)Nm1OBf#idEO-4u>M-ZE7gX(HveNca5H$%7T}hjaBGT3OhXDo z$CTy`FTxad-SyZmr@Knpq5rXGDK{U3-?Vt80v!j@XW|@M>;gxAvNj!Hm|*k&fv0+d zRf(pQY_oZT{2wi!#`jF09;{8_DiS|1yT!fx)eV;#<|#WHF0sb2`ilp?3Xj>{U?zS+ zq~ZJd{&Npwfx&LaaBXKp=thB$%VM6dSQOFa%i#CXz=$#B=;IqK4@9MfxE-$MoYY}h za&w9o*M?cXFJDSXFhtz`blK2=L4aG}Iiu41r&FFBHM{rQ*6@B`axjNF$4Ib^5 zyIBtl@21S1STA~|;}1{6^eHc!YX2!{hgiM-;}^9`_4XeX{jkgXpZpJ5XTC;k+5Mb| zn*FK8@%%co!s4!Pn|)n7(M)e|me~aJjJt7doF#_Yar}S8x*|XS3O&dda-(>W7?<_u gLqFI-2?q^)U{Bb1PQjlkn-e7A>FVdQ&MBb@02xXhaR2}S diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_share_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_share_outline.png deleted file mode 100755 index 9f9141784512d27c516c97e0583215ccaaa11610..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 880 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD4q>EaktaqI1@ zo0A?Z@VI_#y1DWH|Lo<~X+r5uXHr+rdZ+c$^oG^62@XL={>^taU|`0A4y;<;zF}&@|E8HdP5Nm(` zWbWTdlRWodXL+gT?{<|-AWmO!nc2RrHMX+17^a20%$z2Y9Jg`z76zN<^E0pMq|b}a zSk2Dy!ffAE0q-~g)xX*CtIUHN8yE~aYpzu-n15B*)+_Cf&5>(C>*1+)uCP6u$cG>f(C%*7M}s%~uPJ9IvcpnU@im zvBhyBPurE_6MoFjOWc=lB%jgPb)xLMZ6!l7kIIkeBQs{1#h!9Ip5uOVzLx0gTe~DWM4f D<^PWA diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_start.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_start.png deleted file mode 100755 index 907a954a2a1b8f7635694a65b4b5bc433e1437c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 879 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD4!>EaktaqI1@ z>(g2kc-q#AXwLp#e`~9blCqcJoXyWw%##m3lvX_HKjYc+)7KOjn6RJ&`xz&ch;%z1 zxaOuj)s<02eL_ylX(xtB&dZJ}urOX(qImnul6g!Y*zd0A_#@aL^5I6y8TJKITr-yK zWMCHAq{yGpl4SObp+F!)4YR}(bWCufFEhTAG8_hgw0CPxZ3+`Sqi&YLhPQjj6zX}ebm04xQ?F>)N^C~Zcw}{H8CwGhFd5Tqf z!zOH4uC_gOl0u>C%#*Lpt_cjCz&!tc+KqFC)k|FJ@x2nk>ISA)2@6! z;IMJlo=g8@ckWBrTJU4OwAlUon`SgTe~DWM4f DqUDO< diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_start_icon.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_start_icon.png deleted file mode 100755 index ac6c97fa6ec6f63e8d5a991d889f890343f92acb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 666 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDpom-{{GUA7=s@h6eWVDm-0V%j)}R zN5<)sA`?z26!uK&VOTlA$f89vfq(9mi-+sP0{oWPb37GMFtw7^eaX0Uvg4OEtRY5S ztC(|A#AdSvoG|E;2{>u8N+e;<|E&*L70R+5`h^bU&HJx-gSElh^2l8N6;Fhwa(}2` z(3d~-D7x|4*Nltz8p=X`)Yw;Z&tqVEeY`GgYQ}nz2!?yoAF6k{T(E5LF>3IbAlabE z>cP^?_#&yn;llF+#f#nU07(cM4a&c%eL<=lySjJqyn#&b2g<)C3OePQR1TGoI zJ+Uq73URz1;mq5*8LvK5joHc&_wVXjfzZ`XOLUH2T_f_^&h6^EWaLa=jPgpe0q;pUNu;B@Wr=fYCW@Weu(<0XR`U>*K&62 p{$p{%dwcg7*Gv3gfEHmOt{vk{?sG-@%sxLrJWp3Ymvv4FO#uCLB`4x;TbZ+DxmERt*P6u7Cz6kp&EJ z=7;+Xd*(j!f8%sVZq}OjTpzq|tyb3ECwxG^I$425P{8NjY~|Snq8nl|{<%fyG1lDC zcmML1;rFlS?wiltS1f&b-gtGV{{A!DL^sIkD~9jhx7V_6?>5F|$*w!6i9GJxYQ2?V zUbp_PYdX*81!t^eU-QDGPC9#%&$CzF>uqXa+@U1@r_9hvvh7V4!@?cKH@4+9 z1u!o7{>tlMz%(Pi4Ys%Ke-*bfK4sr_ec|FWD=s@K*fGznJ3g=Vyt{fs!FN8VbNkN< zGgWJDignNl_h4Y2@I1bZZCh~XY(~!t)h(B03h#>>F===oSjgb>U}vRok7=F(gRsEm z^+$RRL@_q7Op|0z`lWS+L4nbDQ^`KwV{5q@I3E2x_&~m4VxPfs1_y(=`Yu-Ox1t(! zHaw5@e$JV;j;*8V=eH{wOz-k#kAlKB zy~8ZJhaa973Vb!SLn^rN)wHhLX39HjD~yYDZyR3tH|^lt9X1iA3XZqh`Bk2JOw(fU z`28mPrt`{n#(>{XlbJ22OfxjlV946PhVezjzaK`zyAsx9?Jsd?Vvwu){FlwRS$@JF zdDk~JFTWT5iMh@B$LibD>|5Wn{ujDFy!gA3)hgk^#Cti{zo_Ll3G?igmB`XtS>%53 z@v-w)V=kZVPmX(W_5YnY)AY2~b2;rSNEVj%{7|@e%fBNvu1VM5E~~VZdn#9FG|{GV z-}}|Kk0nYr_N%Q)nE1N-;C_+aQru$uz0DR!KI{(OK5L`<$!A+16)ep7de1jPEW1BD zb@$uwr^^+eWC;iCnme15Q)7F>(bsJ|1$K+Be{Xqo8Ao_wv5%Oq>@Rr{^n?gXlMlGx Y$n5?2#XhG7nD7}qUHx3vIVCg!06}S{n*aa+ diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_start_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_start_outline.png deleted file mode 100755 index ae48df9cd77928037e4095776aa4cca2da6b1003..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1077 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD)x!e}4Xc{`U-LRIK(hFfdQ_ba4!+xb=3{ z%}t9H1RM&J+5i4uFUxys(E?o~Q`6A9`sQjXaxbU8^L|#Z!@?-QfCc@NX0YR{n*7wU zuDflbIqL&2z4fkI_c$6>FAY-Q=x1oVwKPh{G@kKF`|bY%SJWBSO+UY=()NIa{k~;c zQBO0yx0~8ty=eY6g8M?dyyBa+^~F;A?p|l)Ik-5&w;yTX2Rr!;^WxR2{eP zHj-J8U-^Hk!<`w7I|C2gSoPwDuGVUnpgxB!u{Rf=`q&|2z92iY;^iZ;6ozjKc3W8< z9Q8fRu!DC(Znpn)aUGU~k_~Hg8T4C?_npIVpv)|jC<-Z)bv+V#xlh)3Fw zZ<@XCdzW!eyFTcG&dJ?t_KB!QNAZciDifV6n!>YTXZ0?lPcC0M941fLXxr(hlm6S} zg_qyt66@FZ=4Vtq+h9~B_KRbIaQ5DJQ9o|R1|Qp7yk~x0-ABE@914>(w(Nao&hYSk z-O+A_;3UCRhUGgY)NSwDFiZ(8_?4<~V8fAne_1A6JK)Q(XLTeWn?;bHa6>>ynHqb6 zM~Uij8GDvk@P7(Q3b&umb!!jB61t&FpkKgSr7gt_* zmMJ2^%8y&9@MGS?sfBxl+}A3k&7XB?>Uyq8wr5XoaTop6xXHcEK5pm!<5zE8dwE`4 z-=163%U-^Fb33m`;I7vV+gK$Yr2Y)d`nZ1sqY%r1>EF~RFZ>a(q@pXc+~V6l-Nxm~ zby|$GwrW^xaWvG)J%9YuvgjG_XP5Y%b+~KGzw>gWbkpL;$M0=k9e_vjoKmL9F{`m~)GBe#77?@Uhx;TbZ+0&N9@YdSPR6FhKmYu1uiJ8~dx~sd@7+A@Kf9&uR8`apq~^_!Nn{jDfTDdG3uGFr zeuvII6lljYVP*KfmA5p1Gxybf>bCm*kkjK!(U;nFxXPK=0!{#&l^xKQD?El?=w)uhG zKZg%o0ykGI;+wEqbU_;Lf*HO4^)$E~k`HGUF>K77C^F&Zj!gm|Hn7>GFeH6e;ymz) zQ^JPx;A++dz7NGFi1@M^s7qWB%@q|W5htheR-~V%8!!z!T|Mm-* zYfG3eTz&h){~$}koxY1_9aR-pO;2%aSQ9rzit)?oB58(AI_mxmpAw5Im?rpdPXEg6 zAiw;9*i5d5+IjBw4BH$V?=tf;+*x$+U1OT(we)Q-br@cl-7Wsk_~wIO<~PQan$1!b zvi$z7`fqMLlWREB zv033-^?{R%)I7|)o6fSENT)u`O5C!$<)HCTjzbf6+uWJA?qB~>ueaes|L5r4o3Ffe pf0|3S$mUneuhmEB9AJVWY38Df+qv}TMFCSZgQu&X%Q~loCII76e~AD9 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_view_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_view_outline.png deleted file mode 100755 index 33e28fbe638eec9f97e3d50959b7c3a67cdfd14f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1168 zcmb7E`#0Nn82)^H;}$~5=(LKWGWSc12qn>!q;bESTg;ugE4Buy9kiQ!doVgtp{zBj zyVfPlp{}P5+ZfxzsrH1r#8@yI%g)(9u=kwzdEV!o=lSt@?|Ha6qmbv3000z)O!5*l zsCIie2W; z3(M0qb>JxOGC^y?-y8toEeeU~lfArM_r8{;C11#D)WY6A^B=h4o%lmRIr_!^mc1ao zYFcg`L6=)GwdRA14R8iS;WdOGaX_vb^Z6Ra@y|qTs108j)L78E_zGUa@ye~7Pbh%J zG^Z-+be?g7}N$lCYumw89*ConGB!&Y=31dZA; zcrO*BpC-LKDrAF{$`73O#y7A!M7}CIN7UCMt`)^&tbA+83N&jbB{XaI+wfH=_SC=4 zs-X_lNR!o9=Tt1}K>#wKS1)n2(ouP&i5@dL;~}4U7<*mnuIyOI%>sef!8lpc?0eB$ zufc~uqm&3OFTMdTe<*o@OFcAgMvklmig5XQwLEQZ!grDz8iBSVtI^7wDiG*OCu2lF z+HHXXZI`*92%Qd++K%Tg^u;Ju9Gfr)O^G_>P~Q-y*j!g!$nuRE^0+ZRS$5VS?( z_Xd!32jR@;2{YBqV^#K}IrF5ML(96HYoqCOhX`BbQuy$ZfEurqL!ScbKcPb9Rq2-L zasEBo-6!UJ1}7KFWohU3*wmYJvBLrx5tvQQq`YB^2eWElCtxpW*=guBFYc}5{8vWt z)l7$uWp`d=)e#y^3zU@6Fj6sV;3BM)iQ||Z(s_>VXWTR;Dsd9-#yM3LUX-%)Zgv@m zC$%vaDiJosY0HHu&+43kIaE_H)_k_lTveybMqg3iytoN#*DNw?>}{ARH;`=t*tn&# zG^??O(2x2^duoC6$Di)@%WlP47Z*$o31ba6PD>N)JPmC!Eh2}0@hr-c-_WEkeXbLg zUEg1;zb!OdWhssn|4L6C2%KLQCf_>w(R6il{lnAwopJ4l3s2hP`DbH||FxAM%?Ej4 Xa(mF)hm?yG{{%p};YMnAq~-quO_>)B diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_x.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_x.png deleted file mode 100755 index b04ef414e98dbc15274a7525c3242778643c1e39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1037 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD0p`xmLntP|TKVCd=^Q zX(fk(LxARHos|ll3$CBp7TV3&GGnz4nv-v5_eXHXhHU!j8OLga}O*#`e0hLhJBST=sG7i!^Zu;spWj(tPq6&>*d>4~3x z8hoV^9y8w1$(zQkAj#;W;&mR7>yO zb=oiVZ*$!ig#(wfa~1}y*vI8pq2wXa;I{3eXm{O{t4}+hH?_;p`(IGOcb-@M9^Vv; ze*b504~ix@UcLHoi`8PQJVpkcRk{2A=5nv-+r)5Svw-KbD8s(Udn(Heb}|K|e*LuV zdwgrzA|{5FF5j+qT$Pc1?#Xc6>*c#Q(@O*kRTvap`}POET0BEqL4OZ}!div!`U$f< z8C-hr%bP4{KI6yaaCbpTa0IW)<1itHD?7v+#T{gISqlD6dEw~A^vma}=oKJf>$x-Qgpd8q9PHaqXtHIY-1A^dLeVn%`bu@TprS?qQIZekuFa42q_IPpcjsvTA*R!{0 z=LOijYt&->GyltiThS)xB~7OV-FsgmV4Tmcb*p4P_t{ow{)yce&F-?lUM9C)^4*kQ zb_{a)Vef6P-Iu)kN$+mf;hh@-KE2ky*6SIu^m;t=`*R-N%o*>t&aFDlX2WnI?~v3F z^W|}$UR}E$6ZiTbpF_<3?pV27w;0(Lo?abce@JlmdVk)HH}>6^mw22hr@YW@<_Sl& z>*cj6Q&uY~uVq>sGlxy&uK(IO>OHo1=9^yFafQbu>GReP9_qXQboQ0hT~oUkpl$Q$ zjKu5Mgq6FDm(7lBKFj^2chAGDXBp))I%eEadp5zke#hM%5fijDtq#}4+^yJuz0AJy r$iC>etL04p9Zmw}5(5Tsma%8h6FaAHeZ&5<3_#%N>gTe~DWM4f%?;yp diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_x_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_x_outline.png deleted file mode 100755 index e5dcc05019c60eea0bf3da2aa1ce16deec8a6a5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1336 zcmb7^|3A|S9LGPKO=HGzOevNxbs?4=^QCXoFn7Mpx6MW7YeGlK6@B5uxsBtDbc~hM z)S;q$$?|m)VVOwQ$wZ@W9rCSwne2A`2lskB-tXu8{dm0JkJoRnEQ+TqN_Cqm001aA zGRa3l@V`PRDf;q6{+a@?D53`u03NWA8=-K;j;FZ$Im`c#6i z2XNtXKdOyxXn~sgbt+3pU}N$hG#f~7*E}>p>FY_p^Il8&A zziDt%;Af&+M$q!P8b*@wQz#-+;}w|cm?b7W!NAn{i*wx)VoY19SwFLHG+F-sG4PzN zz35iB+|X##8?@8(=;1BU)ms^F%T3m@$HluPEXs^=TCz$Ahh{zU<=Mj)Gf@2msPmwF z*kxSW=Y{D#gq3RJ_IcaV1Oh5u_q}bfWsIO59jU)vZ=dirbxL`iF+?kYp({|OU1U{U zqZ1Jt5M!lr+!cwd+wlW5M-6n(DF^UiKCFy%O4pc>wrAy{CRW=xs=rAoB5bOw<{HF$ zU#%8<9PpinE-Ux+=M@}Tf#bGp#a*s21FD+q%#A*Jt7-FVNs$A&J+&FJGJv zhJ;8K^pab0F?>m+V}R@Wn)Zb@Q6R@O<);7LJklxa``u9GVGe?-u4lP_QvL2irID^U z_=(dui-=Og@FIq@lEI@QLd4D&!_*$qdVbi2NpIRo1P1_=h{1O}dl z{~`+deO`aETklM==T!)_d?jqnrCQyvbj{a8*EU5kvF!Tt@0`Oy4uvzKTP!6Q9=uEC zP;i*nr?n~h)GUU&wMo%?r5yCOp7grL$Pt~pMK^=t&DO);>KInVy6t6HS{C(^`Ng;M zLKeF}AAi?q{QbN@4C9YgDf@U8WTP0|vlvfg{i_#x!qyNWs(XrY#VRRJ#-Pw`Dl9j) zFq%m+gqA9EHawF)u!r-U7V84fhnfy6)Wi}Rl9&Sm8V(%2`sa)6O(XHW%p49mbHAUt zxW1u2)?SFC;A?u$PuE3N{uV#G9von>DeC!sdP#gr<;|1p9=vS+Zf{}hUdB834NF|% zy!krT&Gn38TXtEMt~QQ3uFJr%(DUu%eYujpi&+?4cI^5!+3;N8yiOKD27xD2?%$hd zs5t!vD?@bWoc;O#Kjfuu>+#TGxX+#QegEF>Q#0nWDntg&RErc+7E8!G_a;DSZY0AB zsr$2~cb;HMxV&KR)`>BZ4cD}!*FN6LAa~n#$HXg46&gh!jiPuT=qaS`>A%Qq!LH&c zzm(&HAfHO;7Bz>rKhJz;?bycf@i((bbi<;Febo#XsVJ8B3qC%oOco%sNp$o9ir3^ErVq${v$XeICBV<_<{ z7tVcEIQyYz<|amu9~-Qqe)KnG?mW50%0orT@tfM^3G9}_1^vwI_vg(T-dRNL&~sLSLdb3a{Exv=h1nS zI-?RhH#=P0ePC&p$covnhn}(S>NP6d8gOf_>}S6`KaHa4-M0_N#ZA$2zuEkwF!$5H tCz|)}c1WCDzRbP%{|0D6W;6h%YJOI=i&-ms-_|k!fv2mV%Q~loCIC5QwLbs= diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_button_y_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_button_y_outline.png deleted file mode 100755 index affbcfa3c02005b8a81b942da35ce8c53511b828..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1253 zcmb7EYf#d86#el~NioO`dT5BY>8g=YQtFn0De2@ZGb*!EoU+oCDcyo>`im*Bw)vna zQy0cI&2$zjMSK+GBS{T>lp(2TCFW}?Q%iGn*>C%G@65U9o|${*&i!%(VIh00E!SEC z0IUN8{K8EP{R4P{FnYunM|g2TorfLG+~p& zL-r?yW=joMj%)3ADc!}GPhgw9^F?-=q@Wq_t}@|~PTw8tM^atd z%@#G4Rg-Re1EF$38@!nUTIT9bza$9P_rh|F?uubTwUYoKqIuhK$7W_fA7_}eDkRB^ zLh$yPC)7W3;T(xDbup6!Iw&JcHCf*FgILLfIeVlFjoY-UH`416(}4{2r)JJ8E-BX- zcYw4jZt?9O9e@!=S}vf|aLmdW>nX<%l7XET#q><%Al6#vxT1PhT@oMfX$f(5K7U6WOWBn##$;R`nF;j= z*!w8<;jYt3S+{C5_g622tHy;nzkl74QoXqFP-o#d&Q+7$%K|0~Nlhzf(mhn%LtST; zEhF~NJIX3vWzcj{6o*zlNmJdnfxSLB5aeFJnkq)<_%)xg@&;QKUq4s`c`qeq#M)5z zEKpo6Y{^y!h8{nGu`H>4;!U0sT(lt$BQMa3pG$|FZ^3mKxG%gHKj;{6YU&#a$|C04 zsC&RuVs675BPg}&nBvU>x3%0?F!e0RLs7#UY`_DXMK8!9if;M@Kan@CKtr5$kFPa( zT2XoXg#iIZJ;IY2AU_#@M3l(j4L5^nS=80trIAo>;cZFTVKS9|Cex|38LW9Ry2-zm z1Ze`aYvz245$Zy?S!A&bQtjGlCzelwgw)`igWax3Ec-<5dc)apmsy5Kp4+`PdKFx+ za+N~33kM43a14%lf>v>stQ^(};P{=DUdZ&Psj$M@XSV6f3@>$6ZIShrU$-iBZ{1b9 z6=T)gTFTo_HLHCv31?^dr(LU?~xfay(mOoa-U6sR!u5; z(w zk)dx>DJ$v6NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh0azWsXn`ThGD4zHCC0g7Jsba4#HxcBy^Azzb$L|bB7 z--9=*DJ5Unidga()D_ko6ENq9^7Vh2wqU<%%|(j`zu(qg+nzc3bh|fDa~S`e?OW^D z-jh#Fc&z?9bivnyUVz0NsoW_CeJ-mWV8Ps)7t4_ zQVN$F4l=Z|Suih=x*&O=8>}qC{6fZadA1cM5AqsfnDg$R-#sn;`?IzpD;@j#yM_KN zAfqNUsLguZ>L7adqmx4avmfF9j}zC-{jNS%G}T0G|-o z=Z0?2joe-s02!{&jNG0XIz2Z6F@Rj45}?#P`LIwRi>oBaFZh0azWsXn`ThGD4zHCC z0g7Jsba4#HxcBy^Azzb$L|bB7--9=*DJ5Unidga()D_ko6ENq9^7Vh2wqU<%%|(j` zzu(qg+nzc3bh|fDa~S`e?OW^D-jh#Fc&z?9bivnyUVz0NsoW_CeJ-mWV8Ps)7t4_QVN$F4l=Z|Suih=x*&O=8>}qC{6fZadA1cM5Aqsf znDg$R-#sn;`?IzpD;@j#yM_KNAfqNUsLguZ>L7adqmx4avmfF9j}zC-{jrxWP!PsM&_GSk!QwwJ2PXqL0wqCy!Sd_v_s`G2U*B)PUVeW6 zeuna`_uYVUKRsO>Ln>~)y>*kf*+9TGaC7ow)pz@Eng~cJe`4u;mz&V{$6R@LQ86D- zEeL$5Zph`byw3krW|=R4#H++^sR^qBFEQ1aehzZj!WhUkBlmy~>y diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_down_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_down_outline.png deleted file mode 100755 index f31d0a5c8a0ffcde2b4c4e5888fb654d15bc5c5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1F{QUL#{rl}1=9NxXWME)q@pN$v$+-9Sh9TD>1Ce7N z&)7DZZ*YI{?$MhC?i<84O3E(;29&IJ+wx>e*{006Pc{63OKR>1@64Q>ShS9&z%Hu-GY*X*Sr$MnbrzvI33Vx)M1fQy$~DuZMkpx73MR7Kn)BG ze{2m-Y+T&Xef-vvqBD$jYkZB4>s&Z~OQrY>vo0f-Yy#(mmkn(Uhh;v0PR_me|I(lR zEMRRloDrVy<&zxVhExkIxWZV@^u+3c)(LCtS>HY8&AfSn`>DzF`}=cM`*oIm+Y~MZ z)*`_lpkA!Qaw?^-)nVS5kL&Gv+c$2z)&JRX&0ZOA#+N`f22u|=*&h}u2^S|AfwXwK L`njxgN@xNAC~~Ys diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_horizontal.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_horizontal.png deleted file mode 100755 index 09dba4e577b31c522b8e8e0bb4bd545bb24b77df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 435 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O`1!2l#}z z{s)8SMsCjy-GIy&1|X5=hG5dj^_dY!5D1<7wtE1_nk3PZ!6Kid%1QdGa+I2(Sh|lT6<5zW$m= zBy(dstWYpOk=uH|ME`2`OF3-neUblVka_1H1i3BNnc=7 zRoJ4o;5t*4a6lI0QcjL22PEAL2R<|UaTlm1TxALqHppiD{Q3Il&wqa?E%3L#xTTrR zU`xzNWYq<-58nQaj$h- Wd<%a5J@D}#NXXOG&t;ucLK6T=O|5zW diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_horizontal_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_horizontal_outline.png deleted file mode 100755 index 5f7094ace2626fc293f06f49b32edd4fd2912008..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 377 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1_eEIzK_Wk=A_S;*k14Z9@x;Tbp+AP{xp#v^f#g2`WD>gGXuI0a?FyU7NYvU1iErE=_Gk3S;g~@cRVc`%^aCq>% z(XUUHWr_VYx6_3@8OLv}ufyXJPbP-X?Q!chik22QpRMg<84uJOph}+#+?1=pb%XAXYKMr q`)gu#ci$h|H8uI)MxezGGu6F_eYU2lEp!9!F7srr_TW@bg3pFbUum%c0mKJFJyWW&rK-PK2r6s$< z3+moq33+`q6sQ&i8tyZnnf%#K?bAKq&2|g=+rQpwQ03Cy#UOmNN9({ThSgjzQXH0Z zb*M8zl!M6w@(j1qjd>I!&*bG>t22b&(60-xXER{4bx5+#XDGeElC%9Y+lNc65rQ8s zu$Cw(ykJs7(#`OWU!sIDj-_W;gDcCHT@63~eg0D+KmFdZm%_exUogyh=*5Ply5T*e zUGjf1RvpXF>s1oY-PrHSIDLOv_x`J8SJ~DatKCuW&CJCPRlh^#1KWzErMbRm+rvRZ Mp00i_>zopr04OA?5&!@I diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_left_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_left_outline.png deleted file mode 100755 index c813a64ae1857efb32621658edd14fe7d44c0323..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 389 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1_eEIeH_Wk=A+z*}i0E+(fba4#HxcBykAzzb$$gz*- zWDY79FyD-w)7!~ja6$S6C&%i*!);d=R4#Av%!t4JWu+m@{`}V$?*GbPwTgc^&;WzV z2RpQ0Y)vv0XErt}o601Q52+wK|-*g zh;gZojz8NIqX!2YeAw>nn_oWfxxfNh{VAV~zopr09P=e AwEzGB diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_none.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_none.png deleted file mode 100755 index d36e045f234a572bb4bf39f88236b6f2a3a2d5f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 398 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1F{QUL%?fdf?rYK3NF)%PPdAc};WZZjuV=wO^1A(^0 z>ynMUCpcy@TwH#Vo|KEEt0A!)8v1+)+J!aGw{p5kNkGCXrebrpQo#z%Q~lo FCIEA}rhfnc diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_right.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_right.png deleted file mode 100755 index 0f874acfe7541cf90fcd3c6887da2c9c448a4791..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 427 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O`1!2l#}z zJ~wiEZs_*H!1cKyknQ@+$nBY-(|<4kiU7F~HW~@k4b%t}%;s`P26BW;g8YK(@8{dk zUoU^Zet$ohvA>_eYU2lEp!5$<7srr_TW@bg3N;(>umo<}AiZJZyZUP}3=@J=x>T;# zHhs6&3VnSv6sQ&i8tyaeJeFCUEArSzugW34y)?_gX!#OWmSXOp15cRMG#7kjTq}~m z$_P;nCLi!K?Ebxd&7R2n*S|00xo~smr@By1g_(R4*yRO$I8X448)P#2i(klMG#5{Z za$rN!dEh(qBC!j*86%>XraG=uRU|C;~ z!@&X{AW;^#q~Ub@?z?Yo)@B`eST^tQD)t%+sL}&Z8^oVHU7Gtct~LxL^YlsqFv& diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_right_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_right_outline.png deleted file mode 100755 index 0c60966d78e3c8c3e6582a1d84479de95c8844f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1_eEIeH_Wk=A+z*}i0E+(hba4#HxcBzPM&3gP0pOnvu72FTm_tCp zpd!Iq`%H4zNeMQ$W3z<0WO|IH1tNAk@G`CB&Jgg3S-{lz?8KhC)6RA=H%?_>WMbj? zW0UaohR1;~$D@80$uRBO7u@2$J?3J4zSE%5Kdt7Y2f@Y zpUGwa)R_4bPd|HX&wRt%Zlhqj-tWH*cIqigsrBj(8gJh0m+;!bsldR+e2ACf)5}s0 zr-mK#R&*8jDl^77m8zKYC_Fp%IQ;p1{XO6FauuFguqo)gKd<-x@TuP`RC^dKq)zYN z%9hvLc)YaX82{m0a#4X>LaZ8kKHa|>rG4f)!>rHB8@Dp@vMjjwU*3^7zu)c+!;wE; z4D%Y83oO_e=JF=kyED$&&8l!IzFwDM-bIFhLK%jyPaiOF^|36l_YgVIm&{lY&ZM)L zQB2n1;6;WjY7O65W-xY4X6O?6P$R;6AUWOA;X(J>EQ_5fe9V8DwwxARrSejj@r5|U zNvG4F+85X{?YJcWP4p@|M?xlnS-snZK;Sf~53@6$hZU7*Bz z!mg=x9=mKS*gu@P?_D)_#?1H!tPD>iBf2)+?@66FpEJ*QIQsFALH@r`os7cdtGBlv>R8o%vSd#$>y0BXZv;j@j#61=`m;{; zrt`wDt6wX62L5u7jJrGIWo+owr!L7sueIVPC9l1DZ$`ztppZ+m3a_u8bgd`fZ};`f zzn?yvl7IfZ-&UD(uiskg2k5-fJCfSqaB*VBswIrOR4#rA*`<`}bku6DX2y zBbHSkuys=Hn0o22+{&`)-B;(T&%9F9B&{=RHLpU3p3b$8469!qYq=hy%P{4T_g%@I z7a2mb*tOYEd89GcWGwSQL*&awb#R6 zehFPzu(Ic@o%gcqGgnW&_HM_=yXtzgFFjdjzP-J9myF%#h5KJ^y7{|2JkE7SRQGM) j{#pMM4MEWW%_{me`A2&8-b<1P=0yfiS3j3^P6nS2{ycsD^Yn%1 zMs7f}ORt^=IZB`;$S*kmeE)j;`~LCz^8D}X_s?f=RQT<|z`)$*>EaktaqI2u*OO)& z@U;3ewR9JneRnXZ`@j9pmd%;Fk4{om-gR4Y{(Zh*$Bsoz^1t%0d#R8ElLrH*0;5U; z%ZK?)COa=J`>nb-^xhxi2Cv!IDz8YzuH~OF`;elR&ptJWwHIH=^X!V_TyW27(YCK# z3|~IjuqZif&Xo0jyKEwh2KVu>z%E9Uw0}Q7mc0A@(z&+YuJ^OyvWbia zpDWf(W4KYd?7Ly*mk&I5blES=JAUh=_G`&i)0rgh|1SN^e5WS)tPw+gP5DRWm>8DE zr1k%Ug%$q4h_zu5n9tcDoWQVjGGjrijKftg#x0>d4$Sp`cQUNYRbsGGXMbR2*HAWz z;R8D-(~3Dd4XbA`%rtR0#(cp~iXn>S=v~H(3^K(Nx)~>y*cQTnui{A7;U_P+@fW-lZ!yWl7QVyN@ zjgMPibmX^8Vrkgd#BTC$@`D34yrTK6$4-k}NPV*Qpp1h1vj4d=nxyVcNIk*H@IcPu z4(G#qr91pzY8Om!c@q3reD(R#WOs%Gul7g$T_=5*A@oDD^kViI(=}FzZJ4}5Cq!A} zh3uY_Om`M+FJRliJJ+JHYHsjt1qbmHRY|P5&5{lq^&A9Q3SKkqV41)mbl^Mtk5Bp! zr!(&pozkooCv(5BiI>ibmW7pW|*U-I;uj_|*n z$V|(C84=Y={wLq2j#uPh>Fg_R;wl`r_lS{?sR&H!I3| zKL0qleS<)ck6n}C`u`DOT1`_|cWf^bWeQvO+}u#Pa;1&ks-A@9%0D@6XFq(u>toJO zJ-xPthW&;nm;P)`@m4qAw`S?*<2R2OZ=Ixjjwd?*Uf|(!o^s>UH|9utRm|HtEAwq$ zNSph4k;_lcnQy;t&Adcw?aixijvu}Gdv#Fx{CgeiE~koqUGas#8x#}JjI@W@DIsGW UvoLEXFt;*zy85}Sb4q9e04;bNga7~l diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_down.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_down.png deleted file mode 100755 index 3dddd32954019b7f1ba68dcf96d0c549afdb309f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1112 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O`1g2Ka=y z{wDOC{oLL6#9yq8-)seg=mMsY2Ce``tpoL3 z2EB7XKTNt;`uNLv2DazZXKXmS`{6%^#oH$uygpvdbf)jizxIP?%b8{<8u31}X83UT zw7?98;N310b>A-GOvn+LZJNa}*}rC=>EqPH{fje&JL)bm&X|2WfA{UgcV!{Q*$iJg zU%m}u-hJdiowdWG_M;Qzq?EE(r7+list?avx9C3GgfH?Fm(ERQWbC;9|MZ53FE7?w zGCYdsa1ah)Puit3>*WUVikD&p> z4^Q6vGXMG*r8-MyCB?KKQ*C(4 z7jN`UX@=@}hS{tg-&s4r(KMg`!-v@)9x}-F-rL6N77^29cYLm%!|}R4-fvks#@$QR z?DbV{b8e|Cv^m1*wZHZ0!52Hs9Yv-ocb?p}?@hg7?*C2eo_2ChoHe)a`JV5eKW@=A zRi1qLa`w6ZuQRiYyp}(2FTeHwcVcywAGe7A6RkP(MOeCeTs8+WtZs9e9Q?9x$N$AK z%e5!G06zlN5q2wfkmrFd)0+*yVp|e|g5~hjh zMAbHgg%~b0y)MMy^YCbvo163jkJDvq^&GU$#T|D))(Q*Yt#;}D`U%&SA5lIm8ykr;nB=Jf`#NU?A!yD}4ch|=~jg6^k zxEpcl!imcfH+1I){okcKcmI90$YAZ=Mvu0cOTOnYFOYhmm&v#5W4d;&Dy?oum zz2uvgNzkl RMSwY$!PC{xWt~$(69B6=5)l9Z diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_horizontal.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_horizontal.png deleted file mode 100755 index f19d5c08b7accf74d8fd58b2db67fbe55a00cce7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1118 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~q1o(uw z{wDhq~@xH}1TS$atGt@@46|&odUi@{ax3J?-zO&oAr(|2%#E^Yn%1 zMs7f}XO?b11=J=`666ibm`DT|LT@*U`+<|;0yJ8RSoGbsjySo}VI-uykNWhg& zv%BtwRx7*f*-mWDJQx_Y`14!Ft(uJ*OIL1ZF`1R~ceW{W7<$$lK*Jc z3=6Gy*O+DOd92E>^6lBx#cuH6|L&Mt)->4zH|-RhZ#y(NY&`xyaKo#2w{{mWT(T2( z;MQm} z3ktjk*c=PP9Sj&EIlDe`R!p;I+cCL5?GGcbY|dGUgk}HNePp{J%V5q^z+5M;QOACu zfsyS&d1I13!ySA}{@>1+&QMmP+_vx=!-DJwSN`YJF@8|^qW-^hL5D-i zE;&8%*Baan_NSf}?eKF+SoA^PD}Uo5#y!*J{`YB`2b zkUqOj@}q8@2TezhzIgsajeAFBec$}slRU)?5~C--7oYT(&#S+vL0MVjugJ9X?2-o( z(^{XNIF>SRy?gwDk5=a&N((1tn|-y5-dq>8-E^_}^5?1tD!)FPa&O*!b}559*YnT6 zWbV4`!FX))0mh?;1kd^~rgbXMS{l4dj{VD@;$02NyMA{t9@~A!@UGq6XA6wJ+G(EM zZn^60U;fqB6YO=}*X4>l<||QM@GvGK!n%OAVVI+?5Qy)X-I(JN+b+(YL*d zrW`d6kstE?ichY-ZS&;Qu1U-xiPyhJaV>BbmgwO*(6YQhg0mrV@{vWh&fZFM&W5F4 z+;Q1MsQhkA;C1N-B2)gEzw>_kdr8sMhbv>=7Bww*H;P`k$Edh0T6ObIap&*XcQ5t2 zdAXnQ#&t!`-nyS#?`h7x^UZia`)~EF&(Gd`nB85U}*J>F> diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_left.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_left.png deleted file mode 100755 index a031aa25cb4145db6259810ad9b923f363b7fa05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1125 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~q1o(uw z{wDuN!wh)OEj($atGt@@46|SKhIoXDs^IJ?-zO&wrjie_JybUFsyxd|YU}VVW14K*RstN!hFq?HXSEX~@atmt=9<^I!aM+i~~u zpA0AdI5Es>U@BO^%%I1Az%Pnn@>xcS57*G-#G%_*N9aAZNn(ft{0i!vb!G zFphvs<`5$W{#y(;)FfW=o?vsxYZBbiaP^)cgOv3L#wx2DR_S+QuCz=3WI7A$5~a<~ujZlOPm z;d<}eNer*EY+o{!+|b;^7~$<$9oul#f2PWTU18mN2l#fgnKL}z#*i$;u#FWQe+Ry^ z|M)ch!)eBAGk&;=FF5)r>5Ao@%Q zvw-Siw`EldqS%Ggu{j%KDm!H>Us4g$^TK@cfwbX+7 z@BGidWIFn|lSx9WmC1FHOYt&>v|b_KRbJ&0F1Gtz=ISl>`CfPGMPbLO66?*r3omWZ zURpLgXzf$+)xzLo`fZz?I%v@QhX})d1=hzrYYKTFU!^}T_sUu zHEo_jkz`u?v-%uSaruPn&p4zie?@Iu5^S}9-ICAK^Cxvi-4sf1T(f=d&dcYSN;iAl zxWjhti&5d$fLpdECEqwhl|^=*KKiz{-L!rAg|Cm!KRx+-?dSR32A|)|J^L%uN!wh)OEj($atGt@@46|SKhIoXDs^r>GQ|#X@8zRe_lG0JRB}1o;K8KcC;PAMbC^e_#In{QCV2J`D4e85o#*JY5_^DsH`<{c_T6 z1)heD8xOKScvq{^_`UwL?X9#+Y$t`L?Y=GfMf$Kf%Zr|XDqpQR#SQN=ez$HuJv+Gym-@=QLTU7zwU$n){H({g08;S3>T(9 zR}^4mvsIsVrfLFSKEEKCNIruM$eZufa`^9t(4Ka>x z6$cwf!=|Ql3Hsax>@UA^IjoJGW7KfJ%xf)Ei|G300t!yE_A*I)XO#$NytI#DB`69% z{AJwtNA*t;1AA5eYz8@n{Sqd}*XkV5z9}PpA}fd4!eQab@5Lv@+}6$KDL7u>rGMvB zLJC8kpU!p}4j-orwxSQ-eJs)9_t6OxS=y3zzCCxLq}-gcY=-Z%n}cm^=Bvv*c$r#V z(WkC4i;>yl9+!$nSda;j<+&u|+NP3%3sr(GlRlTQW?Jp|RZuh0^RjN%e3zw4>0Y~Q zgSN`rM#@gA*(UQKtVimFTSI5?h6|qA(BZUFNf>e@-0QZPuIqLuA1PVktQt_lEHGf-{pHFA@p1l@Wvi^Hk&OaOO z+-7&*Rq19sKhM6s{2t>vuMeyzk8b+*@7#}qN~Ku~-Ug|Cl^28MB`pWm3g$^xXC81a S-)#@fvkacDelF{r5}E*Ly8?Xx diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_up.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_up.png deleted file mode 100755 index f18f7189571a297731bbb38eaec7de714a9b2099..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1128 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~q1o(uw z{wDq(4)RMoSKL2_8{FQg?3%kJQ zMs7f}`TYztfZ7B~g8YKlpYPAtxA&Kizt8`^e*S(2vHH^g3=GWEJzX3_DsH`ot4=?X|&G!2`L)G)=GdA4Z{qG+`v$>$* zh*bZ-pTnqQTcyR$a4ntb0gFUKpAkdgbjB~6MGoAV&JgkA^?BBg&AJR6fqV*Vb`9N= z7(TFbGQC)=l`ahh*`8C zM&ZOXX67l%8YiMB>l|Qt`Yz#-Pdd`YN{#v;MxR=n$InPiPvu z?&dvwYc|Mu_&8;p)SG!r;FR!W#_TSkTLEI(>w|)fO)O1sek;k}rnbDI$}WA;)53f6 z<~?TZV!O+Fn?ppyNOhKjXM@?)jG|Dha%7do58f}#+NQJ%(}fT zJC!l2?7qNl7Q2RxISflm&#*hpI_~i~>NKOm#<{mdZFFN8);#OtlGww;p!Mj0s!SXc zgZM;^tf`Xxx)Te`fD3ttnM!NLD>K!i_b}w|f;IVq+1l75BzNV_qy?-Hj@p0a!!1E|73QjsW<)qgoV%Cv;X?L?@B%#e&1b# kuhvI|G;jq#v(^LYYZuQ-ySlwq17=?aPgg&ebxsLQ03f0h7XSbN diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_vertical.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_round_vertical.png deleted file mode 100755 index b1c36ae9a02d25405346b8d45e9a6de19ce54606..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1128 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~q1o(uw z{wD>xhgGb=|*i-1)L}-P_cXSKhIoXDs^IJ?-zO&oAr(|2%#E^Yn%1 zMs7f}*{UMgfZ7B~g8YKdug~xI*O#}Cf6srve*S)j3X{oC85o$Sd%8G=RNQ(y`}L&R z20X325@(L8&9;`-tNXwG&X&!$%h_Xd~NP{|Axep#lnRZg&iyt7=#>{JQz3? z82>D1SbJ*g%lg)fJG1^vIjr(iZ)3Fhe0#U_gFI#q zmg@iWH!?ijw~LRR;gd0A0TYh{(73QR#wor!4a;~KGahcY7e7#z%ixgznDIsr|AJf} zh99jgj8n|?8gyqeD498&XTD%&#;}F6shX*Yan>}(gbUHO3JvVPHaGDFJUn`hhw%)f zx55wZ2YT)cCLQ@M!tsyEAwKy)HP*I}H36J$QSdB;KZQens^Lwty3#_V;$1A2Uyp)?#9)VAEq( z{OjNH(8->iNz-MQlh(Qi8|zkTe05LHB9A|<-@Pra;a0E% z%dzPP7VKe|b>}*puH!q_69Ey$%5{h0OygnGdS7B$+>Ol)ChuSbT}M;2-1vrJc%`Z4dZL6tn7fti91hN!u_ZQ#A)`orNATqUhLf_lMGG0Z zzTFYH&0@!3V~VnkfUk<2aCfzM z-n&2M-+ujOykj=0{Mhvy*Y8#I`sg*C=Ml@i{i8q@kJ1(BzVd*ruhEXWOq^`uX_w&h#wR=sA+x;;sf?{?60d zothKYHfdh^y0oa>w__gMbUkOX^Yhuap}7q~OASryuZX6-*I)W>zpY^H`c%o1)nE9# ek+M|{!6C>Y415pmU7B3Usx>{!wxhJ1b)~asHnD>ZeH}RQ2J|w-Tt-V%Twlj_!!7l}*sw^i0Y@>(C@#4%a9H#%{3XDZ{rUNPXNkOislQ3$Rhj*5@8wt8{k$-1-y}vP zYaSFh94+e$ayVMx10>461vS*NTI(nLKmYdhyT7+3K9pJKzq@~};TTL0ANw8#V}Yfy Ty+7Xdf{gKW^>bP0l+XkKBVDgY diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_up_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_up_outline.png deleted file mode 100755 index 0aa2b5779d035586666c72f8ae3fcdb38682cdb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 394 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1_eEIeE`ThGDw%p-Y1d9Iiba4#HxcBykAzzb&Kx<-F z-vhxNjWP!gG0*T=%j(`BED*K$2wzxdn*YkFk!5A-#)eYO?@PV@NOjjc71b4U0*y%H zmnrM5T^p%;^}r!Nvy8@bRja2sJY-%hbfV^fM&lLsD%FIOKxGUJ2kIFo%X}_Qo_nO| zO#T9g^w8BD0WTURGR)*NVDTt*@NalNz4~S1*1~_G;uGelfwesN?4bGguO`cuobx9S zTQg?K7AQNcW7cchvzg(n^;u)~83$U6Voo={4l7DAlur$p0&DuAx`5|-1jm+(GTe<; zvyv4jxEKFf8~(U%P4Bm-d&INVj|Vwe0TrHMt6<>gEDD|WXYyx|K2KLamvv4FO#nJ* BqXqx~ diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_vertical.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_vertical.png deleted file mode 100755 index 123229ba99a2057dcf716bb79d75b4f649bdb580..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 422 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O`1!2l#}z z{s)8SMsCjy-GIy&1|X5=hG5dj^_dY!5D1<7wtEp!8=?7srr_TW@bg^EDgruwD$f$Enho@<016 z+byY;F^;Qe^8Wa9YtwE0bBsX6AW+A@;hWvDPm3gK_rGH7|Fp4PYJ=!vUv7!+n;NWV z%o3(CMM+=qVtncVRtzP6Fdw+oI_LGtr!u>qN-S8pX_j(BNUN%}L1^Y=PKhlI2U#q1 z56o=P;zQQ+ojv94pB$!1ViSB0EMnF99)Eu6?~esjZ~D&8UUfo0Oa1wR!gi5Y=}JYD@< J);T3K0RW!Utq}kK diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_dpad_vertical_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_dpad_vertical_outline.png deleted file mode 100755 index d919814ee83910670b75bcca1545cbf271c97f66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 376 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 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_lb.png b/arcade/resources/assets/input_prompt/xbox/xbox_lb.png deleted file mode 100755 index b7b55df791a184830d799aabedd69c6855570d3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 589 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDgPP#Q1=b=_XT1 zIRpEKiG~a6{$#w?UViep;Q=q_hHG{?%%3bCT+Y117Q%R-J-SkO!vq$Eqo*14nJn^` z`5q_}{7}BX*0$o^bjITf4bL{eozY;ywZM^~mnn`v_GOtvy!B;vi{t|?#~AW?D_pW2 z40rRiTPsGEJN;6h8}MFWPk!}h&l4;%#~6;f9bppL#F}En>SO$pA+O4m`G8D7>Fe#w zQ`+?w*s7Q8Vle-Zry*qjp-gAeQd{|sSznctW7bV%9J1Cu}l1B(I!BZmW= z`A71=wi_GvD}^50|2`t!iZMANI9}sApTRZtrikNa4Ksy8eRLacs_){s%CJ@_bB9;M z?>|4YHl5bantt=O_U8h-!}*~Mzb!j`&nKCyAH2qJsQOJw`M$!P+*%Cx_t)RhFp7)w zQI>kJZhC%c&b~b&SABnHE%y55F8;=&;oFB(xjjp5IIo1(FKsNkZ!Oo!%yZ>$Jm(4F z1l8RAOcU4-ygtElLNcMxr{NA`^%JHib5d_G$emz#%06fQ{`-vTm)Rv2@*QA&aI=|} zMT7MrV{7borU$2Yhlwuu@~W9NW5ryp2J^EGk}=Ju4VU!VSbQER#4+zP-In>LRFg>2Ec7&i(toO68Boc`eL=gb53h6T)SX$RzHG76h3Nai^tM7Q0Yxxt!0 z|o^!|+f@7Fdn8?XM;>;#Wmq*$(Dv`9QG|J@-jADE&TJYD@<);T3K0RVl7 BLGJ(n diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_ls.png b/arcade/resources/assets/input_prompt/xbox/xbox_ls.png deleted file mode 100755 index cb6eb93afb0ea96d3939ff7f01499e1da5413736..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 971 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD8SHKh49x1DE{-7;x8B~4 z%sZmM!w@jZ;fCb>|66jGzLXM^F1Y3OWBbJqA_@`(M;}jj);YigL-wpJ+A*KxC(haM z(5#_Q(@8C|&iyC5NG zgJj7!Gau7SFnn`9We*h1G$BR|-NJ8c!y6G}M|UEN7JQ;df^|Gl#KvroxVC z3~v@qV9iM2>}6Atb2!8_BhKNUXh-!jhf1X<3=_Ep3K>{)76vrj5vcheo!X-L`;J6* zw)gK9Ele&}4|WL(3*KZ8Z(y}LkPvR%&0t~mz(suuALEA<2gQ%~0y<% zJARkrgk0CN?cqKQ6CQb4>|)r*|KVwL`IRE=^ZY+#CNJckaEx*5a^b^8K2^K+%B=o) z^+NbA2g4;P&yHuka84DNb#RHv_X0yz>s^yBRYlEX7U@5n8Xxs;1%t+tr;Cb{lTJ3& z#LbK4GmPHszJy7kJYMG8y|bC6)$`botUhq!-`kx^%;m^zD2w0^nL%Ue5#`Dv`5rZpR-QOCU39x(Y$^@+|y6vsaDF56*mN*maM2cbj0wF zCu{Gv+5_ zO^X$H8k)NrVs3q}zx8%raP(0Dp@nyEO8(p#{gAmx`DEj5d$|I}7zXhJtPrZ6OJJ^% z-JUk{Q%Bb`>Kt2cZ8XXJ^mE2J^WPttvG=!1gXxo3{%pziED6W0i+jE?GyHj)%Hh*s zky>Ql&wsF9H+Ld-zJkW?Foo#Co6Y!PDF4`44VqSo3}U z7hju_q?3#ruGv3)Di=4|)q(NADqr>3+L*86=NQ*mTkQTfyTR(9kweY8`ETBS+3;HC zBa1`E&i{Wznq?A99eiKgb7Zk`YRQ|qpAER_k=eNXhtJ!>@0X2vj2N8jRi&6KIUO<` zx|w7`7B1j)*vU})+Q{UoNB~3hW!Z-nSzA(>f9Oe71ZbXT-C*OdC$X=FB_NFPHvfTX zqKwQQd-)S=n~pFyoD%N+Xn)?kXCsF(QyRffQ)lFffo zmwA^LuoUb!cHulwP%|$r%;w*})yWJJXE-i0$W<#;pOKJcJMdnADucsg|1W>9tV)RK zXIQ}(z+m{0q5A!15w(VrhMxv}Ju+2I<&2Xh6qtdtcS zM44pn-B2{L;Y zI{T$p8) zn`|7mPGVu5wrC}zjo0bS;BB$9f=pho_rI>$mAH8N(w9E=49eb@*KS)IJ@4GTkLi(u z2`}fYTw=7|LA5+~{#V(lRcx=$Jku+ZNPp$V&Dy>4v1-MpA512t?Olz_{5OXfH_5r~ zmT&Yc`MNqnboQ;&)9f!z(YmU?qVwDZm5%I>+jPza*zOi%a{D+#!^i38%JX~w?~MD` zvWwMTw{txk)2Y=AuY0dvJKzu{{9x}VzbOUk*M4nP6T5uxNYKYu2R0|axwfJ7*38?d zLgt2TH#G{)wOl+k``LEar;>|zI%{8FCi!f=L747mv+%F8oQMT3GzLWR$6_FRqJ(Uj}UUza<)XiYYF?hQAxvXEQ>vQ9-b^1GOh}XIst@&OvP%KQJ>6{Y-Ba46om^kp9smcGi_S3X| z?IJ}bN1bAhI3OM8m^ODaB9kmCwZBCce~;Kprk<5WPg9l(8!R~`((GaWpl)vdTDkh5j6JF+ zHYWawTw46Pjz28j^&PXy7aNwvQOozQ{u;I4Y!1izSL?Vh37f6vRA^ve0(uNc{9sc2 XcEeFt=ffgk#4vce`njxgN@xNAiP+|# diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_lt_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_lt_outline.png deleted file mode 100755 index 8792f4660837f21c30c7328946ff6ee03cf07c24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 722 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD#k^5Lkb(PKnCH*$S(h0K4U@c2o!6PjlJO!{beepW73Ymt zB@PvvbU7^+UjE#mX~%Jo;rZ%SANPyr&tzb6U_2DWXD+P|a?gL}hX0}n#~p}gNU+=K zaA2qXfhy*0bqsR%b#mD+eC$0`*1)^dH=MU1E@l^_*n=cvhJOF1vS<^Y-I1}J8>#?jYQ-<3)oQ zLw!uDul@lB|3)>2cLz)ta2`0{aN>S|^noN{^^H>*SNk^|2%NgBp`d>E7e*b$uA)ML zXkmeS`yW?}A9($5y86Sf47YZzR<&S^=xV>OZGPyx&4F(o4Ue@sr)G5?4S)A@;c-ry}Gee!-Hht}Q*H!(~*(*#K65ZgKf55EPcEs=m$6;|`s$%eT L^>bP0l+XkK@=iti diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_rb.png b/arcade/resources/assets/input_prompt/xbox/xbox_rb.png deleted file mode 100755 index 6582f391cd6050d2deb7cc79889774fe751c69b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 684 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDEaktaqI1^ z+eOU^0&EG71X&92|IfUgGRtg9!-Cy6&i$!=KH*4yOpIUP?V}5UhM|Fm{oEJIV(go> z9{-ayzc+0&!|jVZ4e~4)C3p;5et<2zF^Kf}pgX*;xzq}4Ss$_hUE1RJ5 zO=2~}Jb8z8Oa>ENdY(2!vN&v$*bo%mn01j=?!8kyhr-Gar;joOg)=|8yFgW8R?D=8 z4ATs*4%?<>472!|tA~+3GyU54mkgx!$Yfzw|K7&Gh!uSsvW*{8l>0yy72wud?mBEB=w;L3M^~Qtp-1 z*C$Ug{rELg^_E*?!$U@v>-o8#we$9yZ)Eg(zAZ_!;(u(Q!y4gzy&Ni>FG>PFB{z7o zeF*EQQDa@k=pehYrH4WKy}b3*34#ei`5ya(jF_HHl2YV+A#6T}HTc7xOOAX}Wrqp^ z<`*}FidiYVdc8MQ% zIhc|8CUR4-R`fh$*CgO?ND=C?qPrknlIlAdx4bR*Dw`Kl$SFZUz zIMtu0-dH!m;*gI_T=uv22PIEG{!NmNb~QMs_k$gjqR_yEF8-s7&RJYDee?k& Jvd$@?2>|7+Hs1gM diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_rb_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_rb_outline.png deleted file mode 100755 index e8a78e81d10834dfff6c8a2ef111782756167d16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 800 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDfFZ%gLM7(?|1G{!zDM6_9htht`1F1r*ZqyDmTNlX^_m!Y5*U~z4luA8G{BjE zVjGGMzl{q@t=z)dJo6mW9FON+ymvVr(uEyAHdhH7sI4+uRnDMzQFQ%lM!kt*irS2m z<7a)lB@t}B_MPt;gLTd~c3U0TuqSTr%zIh3y=__t?l8V9>6&Q2QACU3uFO7>$h=qm zza0()U+_2S{5CZp#_)mLb;kR175G?~_!ynH-#Rn#L8{_|pUHnuwj?G?hek%G-iLMA9X?P z*OxB2sP|$8!)sQ)8!bE$R$L_w^CRbe(=uPZMxABPHSPlKAJ%;HL?;O(l)q}&x9{M? zK-P$|+S+U9E4hV@?v|}K7h;Gz?ajY!f`?iq+reHIfwNYNw&@BqG*&;eb@)~tynN#n zX8V~u8#MoJWk}#KTe@3>f%RdUYezWKlCwc}6@FZrYJ4|O= zo-nj-Ibft{=`j6Chc+Wq=0bg$rl-@-9$Szo@XWnpFT25XrW7+aUg1EN;|p>aG8iA% zX|?GqYcSrF_!HGJVRc6^L)W2(|4bh~Ft$CI#d2?r()X4=xd&Y5S^r*q`flg%L;nO8 zc&^V14^Q3qAxA^_ZEWs58*abj7nf--I-r^|yPx@+|*^-@H?g8V9?$>Z0%`b i|MjgP~sJ z0DEF^O~fXpAKMuvX2Rmq zLxvnR^KQlkdl=`=R@f25V4^9)n3875$>w3hz{z!B3*%E=f%iL^zIF#N6~r*`u`Mv3 zohfkOCM!R0(S>g^8k70HAB$)Uc)eJN<3K6HK8>W^g2H?iZ0QXX6dBg9%UW<#hk-#y z;K57r@&d<9Mg~Ey4+Yb_9`0@8nr!UA#$0iA&Gs(IwfqSW7`}7wPS_=Nne~PsLwsuY zu_<4x#SfSy7?r$yA!@3vIEAB7fEY8*OTIR0v&*!Rsp?9toEZ@p4z{p@< z_y14u1qt;sMiG_=Pp$V!{^^^%SkJ+Yxg~g&2d{`!!;e3651D@1{g}1liv?P$*rVRL7y<$e3h*%kfoGh}ApM^$ZPlR@d(Go3!g%EZum+f~j_;O11CHhXJXz z>4#rPS6|UTnfF)MHpg+RN_1P-rLf3(-O&NE?W;F#y7O)Bs|~Dja_S1bo-deXwk?R6 zE`C{YiD|aU@|~Mlmwr#$-)pwK_nS@lwgYoHd!+Kun<{LONj}K;>Rcmp*JsH&jC~s) zJf8dIxai&+dc7GtmX$o^{;hqyWc$?gzoIsprZx<0c5C`%Ub`RJ60`3#^D2HZ?!Ujd zUmZQ7{p#z~<9j71p3gS@b~CT?G@}Va2ESwFpVKQB*6cD>?!P|kKBLw7?PGpQz9~?NJr9Y~6&+=dtoiPj?AS6lt8YI_TkH6J`cWSr7ji z7I);C3%He=+-B?((|PH#+&;+jd3xTV&ZvVz=`Cx_8@#V}1eh;wIm`H@*P_rg=vHj* zZ#i>+1=n{Kg}d)$^DF7S75ZD4{HbnnZq3IIiM3|V@}~a}BWI=m3>Wmzh%M~A{{)y> O89ZJ6T-G@yGywpsR^Ju? diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_rs_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_rs_outline.png deleted file mode 100755 index 7ea3310ae146948d1a34640f135925c3d04cf1fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1354 zcmb7^{Xf$S6vw~YjE%-T)T9>&T;cDorFh0r8Lmv42mSXZc)N|DE; zOvSipCc31HaZ8x86t!F`YOB@F-R(cPpV#Z0_c^cEIj_%epZrkTHk9TXO#lE;RDa5L z6~X@s0aNvl(&8^Fs3mv@djr7jGg{wb;Hup*G$_n><^M?aq&i>Sa$K&O&z zhtEt$=bG7-lXSY?0n*NvlPafLD#bhO@Z`c}S&0O@3hdTkZhrkwxj^;@%^ra(Os;dB zi^M;?G`!WQWI&$VIgUz$O#v>VA6T&li9v~q-3-E>Tom){Bng~tnG)(SKPt&%HwIlG z=O~#Z<7%ZIcnQ->thWQit$ZIWblh^ZBO}Y^d$z+CU(QP-cRer~L$a%=y~q+`mtuej z&M>RLApDFvSu-PB-TKw~^8S1G0!1xSU^8+xlyYpL2_^cq-2QrUa+g-xNV-_}mc$4m zkj3UT;Ta1?Knbge>9hhBmGMRiAC!TB)g}b%1l1o{Ir2)@kF_^DZB)lJ)%Y0uqc zmppJLWCJc&h$LHdZ>R!HxEd#l7d87LFZ_fgqx#lT{Tr>D@`d5ItK^~yc5J|$E`Z5r z7FJ&SP;uJB!Lp}hbe(~>v*n&(sx^=It^z^PCb!hg5`wS9))Y{r);Y1}(LqHG6WUy0 z=n?-#(@bLCE}G&rV(r0A6AZgLjxF1mydHX4uNUPQdBF2^zGh9Kr5xU#<{n9e7c^7O zLVJbM35MKTa%kRWq^v7taYvjjzr8UC-v{Y$Cp?o$@}9^Z!-PB*M9_Uc;O5dm(@dku z-LCE#m!3F*_J~hMEPg=j%)uN60>k@WRb#{s=r*TxPYbWgeTphE=3Nd;awsrYLfBXv z*AacQ+AaNp`PD&5r`z4izq1gC!9CSS2A3m$dHaPwE63YrzmE?-s)J_IPwH-L%LE~x ztdjA%PR(gXqu*jzO&)lXZul)n4*JUkx$^5^?l7un(1Drc^2EbM`Wkp+WW$B$v+_s6IF_& z`Irb?za@?$(PIfzpIdR$Q4V8CYcsMViw7f6&ZfZ=!j!glcC+M;$@DMr=e;^|+I~h} znGZ{(my23{Q>5;4%Ut-&|2dC1wbC=A-UdklO{G6TYTe)Vh@sHirb;t_>Pw?EZe&I3R^_DQCx~h8rv)4}%(*{22GiZ(L{R&%WHoPMZ0_ zGL3`-(_Oh)E4UIGj-TUX-@wMRL;tSB0kc@19qU)T{5j89MpoTG(DuHeT=i*DmLGQ> zEN1w6pP5T)LlXPLCxQJBuiZEt&&MIkqmkgSF5*DI#)goC4NYuo7!JG*zma>;wIOvo zt4_m8ThaI18dw8vHQW{3z#x=p$}`D0QH|kS)B&asQ~o*##PBk+Zs5rCFuOGG!*j6@ z3?9L6&g(v4?5JC_jDbl?;m_JB3?3N=y4Z5qcu!8zopr08l0`X8-^I diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_rt_outline.png b/arcade/resources/assets/input_prompt/xbox/xbox_rt_outline.png deleted file mode 100755 index 862cbb2972da477462066db04f2364af59f2fea1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 824 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 diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_horizontal.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_horizontal.png deleted file mode 100755 index c513f8dfd42999030cd653f5acf589e9ab236bde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1257 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD8fBt*@`27qj-uY)%}`<~nO6K@(=D_3#2=d@AAOxZU4nr*fq|!i(SU*Nz<@GeH1vKNbI_}|uUcF_M?%dpwHHovgYTi>tx z;WIgg^Be#D)h|f-_LkwE>D)CF0^;K^N*RljK5)*b9?Wh z=ilS2OCCIyIrjG8EGb4a#oLbem$#T5v{nmgUvf5C@urySoHgC|f6INjxl&8)&zs%< z?(xaW%FSX~Y0vo8{hwNnyHu!(`DDgLgPO0n@h9;B5Ukp5zb5Ah*P}5=zdin1^7sJ|b?^e1e7&BPi zJK8?ygL)X3)uT9HHue1sU${c*z~k$=I}a^uS$%ZrjV}xmF5GM{P5W!-S&{sSvb)+0x< zfL_Zq%=|xR8go)okuJk;rG_p2B5I6=4`t;Qw4Gjrbt*&~M`oJo#x$f_O;3wh?Ka`; z!aw@+uCFhBmC%+W%DKvRWxu?ZUu?yyBc@k3rSRy~!#hE8@`a7K`< zw9hF~?Z%xY{A>qy9obwTQ@ObL^Y;GJx41qO{$f1Hwe60|9q!wyn;=Pk_1gp#PcqjZ%Ianoc_S!OQ~UAkH*qD~6+up0E+33qmDbc^ zmAL2K&aJus_dSnhn(+LNSK(XbMXIm+6@yk6v8nx75N=Sykn3|RE8WF?Zi`&!szOoK z%|8xsT-EgI)li)y?EE$>?T>MI*VUsDZC1jYBe>Q|`RVNNa_G5gRP3cMs`E5VP+!7v zSFU5UNl5w4@U=f33d(1cFHEk;{q=Ts-^0?=E83*px;$6D+Z@lc8^|7vA%MOvzkx)esxSueDt&U@TnV-v67Y344g_|q)kbaX>iXv49u(&T^VwW4begq z_!?HqB~-C2_@_H*d6i3csz-w8mp$LR9;UG;Fo&3|l-b<#bln|Lt?o+I>23QRt?RZjZ&q5G4V_57ti6!*9~h=5;Xufv2mV%Q~lo FCIDu7MBD%X diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_left.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_left.png deleted file mode 100755 index 1cab90bf850e5a168de5e7f9685175be39d6f768..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1263 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD}HhFuxIEGZ*dOQ2} zq{kXO4u;Q?34IU%#n_Bf+$?VCGlOhK}6#eC7`}q(91SV34d8`c<^Yi0i|}0)B_vr@xE!>Nf9~ z(|Wa>fyw>X`~DxZBKw(HtWzQjlQo(YR>w~&`5v~D*OaYb()(4L`2#x-bG5A6`e}_# z(i@g13jaau>INF~fpS5(nJhyY{v)9pPbUvEiCWZ3%w;caw z@dOBnES)9GxPkH1>ff!NEONZl_ybPG8%IfTY%wkFihg~J^~Ncdh=)&SI?Q%{{+}go z|FwCS?jM?DyVB=<*mWu>p~|puD>YB-0iS-Lhja;d8Jtq%CkMPE?%+yG+F!Z zOy5<%ejY8;X6)T^Yl5AxaKim{Q+x6<7k|C}=)7~J74L)Nzib;sefN8IuQZq=4Ah?;ayn=LE!+yNH3FI8@vrE+|vHW@W7_S;^( z>*>KucXTAYeRsPotaO?yd9TmtasQphqn9L3Czvf{y%j9>XQHn*W1-v0$w6#8e}p;T z?&n!sQ;OZkfjz7reJ(RAXRn zcqn)`oZ*i#=K+fYUhEP3nH=i7TeiMjo5jz`^m{r(N7jSLXrudU-kGn|+1&GV->tpX zkGNg#Gi8)<$3A&)_NA%7$*6CNmR4$qPepP`L^0pC{vTVl{=N( zeCf-EhA~$mR+)TD*D|6^cHPx~aL?m$&g-1VJ56-mn{Xv2k*`Y&$4XXMj^-8nGCyV(CnpA$;rau7K_?Xc5xDe904RNm$)E4 zZmF=Ygzzpir&$^PC27F!Q`#&4qHalcHNEmgbs%xNE_50mlP|C<`Ksv0zu8Br>b>!X z8hN{L_4I7t@{H~v(*UB@121(db;8V3si!d`m|ep4uDCP;YDG`7KiW`!7%$CA-W?8_ zsl+^z;@)Gb42!}$jQggasuO>?xdjN<@r!CR1{lh^)t z`#bG_e$%)I#ZtS_XFIFt>SPGD5XGtdgF_T9i8+-q?$IDB1vo!Pm>+F5y%mSyj+@!O z+Xy%0i;fuvfl@)S0Af9iT?^nZC;D#44Y~8XBo{5yKM}CNx&<^N;Uf;ZzTnyi43;VxLwN0R8lpFi^Wwe8E+F2N9yZyJ^l4&ve_r zLPd9VE(D7>lWtUfaxu%2C_>0+pkXDx8nJMPgH8YN3ARfKm$+YVBICMRPTQkT2oq?bAV1||g!7j!wcA=u)md{k1nyxkg}U|bj_C3z7y zT<rp~3D=50@k}Q}JZ@zw2Xb?}| z=yk=5+}z%6Awj+$WIJ0l{Y7xBI_*f+NYm0!xJ{9=1i>kW7}^_rINHjO;hlWVC1Y*8&QV MNg;lsos_))070K^b^rhX diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_right.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_right.png deleted file mode 100755 index 256df0ce0ad555f44a87128a27e37eea478a05c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1248 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD+S5D zla?y*I54uN1poh^y?oX3{O(x}Ojh2ad(2qdXV`8(`fd7j2?pi_2A&2+0|vGO|2ZFo zT&g-d?V9IKdu9Xws=E`nzRIim)k+}tR2Ro{cJeMjt%s{PrF zGmItMA0MvjV*bW{C}^t_k3#i|KaEQ5OPrGr=gqQN$o53vGEt-E$-4bV7lw1Us|h5q z@pMQ|)K8lo(9goO#{B2*snT87C%=olqTaQLwa#uq_6d(^a~Qs#5@^^zkMVB3)ef%@ zk2QoC6LuXn)KFc;{DiHcI+fu-jHtu=T!xwKhaWc`@I6pJ=lwmd0t-bOqq~epN*Cv| ze-Jn@(_w*GhU^^CLWXagOcsK78195JUz2MPWjV2Ux5MFtJ(Wk<8fx3Vv&uNc+s%K$ zEO2691JjJte3#i1Hq9TEN?OM61fHzrj{UsU8=(8 z>}2~6yD=JMOn{ojEN^s*UTNRES(-}Il4rFT>bf2r5e5%O*iP+u7AIyukGj!c`n*U?D>EB&; zjY%9UpRT#~Uq9ebish-SmMoi$KkY{u5QRV;8q_EQ*-QC6bAq-Tz?E^SsaNoaemHbACIgy**vjR0*m805z(s zlaHLNf29n|^TJ5+vK)$$4jv8w@C(<;LY3sbh4%rP^VaKbNM`Zn=C}(H?Y+LuY=$BGgmb5YPB=KC6p6ypM jYRzT?2gcL%-!MeZ5lgxA*yaFVv{RY6tUWR*fWBc z3|m^-n1se4Ws_dDbyT z%zd@&tAM_r&0P>Ri_hp}*ukFk(Qs|>cBvDueFH04`DyBz5H~0+F%i=tqt7`ya3`-Nj@i$8hMFgq# zlAe5UE6=l-`-WBESA}Km&%3t^3a5SN9s%ZVe&HGj>nOnBhS*UGm!j^#G;U&M40tvttPmtVSsU?w?r#Hv_WEVPnOQL^XsgJWI)gbw z#JFN5jDd7S`)k@Vvz0>Z%4Qn>n)N?o5h_FVc3T+Kh|X=X(sy@r-&lg5^BGOxplhg# zH!8L6=+p#Z)w!?TVpCq>md)ML&0L~wO&`;A&*APIGTh)U&BBzFPQ;^3cg0%r46{0& zrz*$q(Iotbrmn%;Z^Eew1V7GPliq-*Qv)%e=<>lp{8KlrXmPvC$4IC1D?w+k1yr#G zr0sM5+w54IZ?xUY^>$6O54_g@w3PVsw>uO9u zs(vaz%hrDsd(qZd)xOD55Cjzt9-KQr9?r1VQ# zYJbN|_4N;K;%jIo$%2f=>F6PAO6+}MTTL40SK4&r@ua3W&O;c!dMu~%x$w^>Mnhcn z2~HTXWH_l>YC>X!5R+CGvfoq<9;ir#PwTlhB|5xE!yvbQk|yNgdA}*w9(l$-wC8Vg zA_}aEi6bZkkUgEgtAjm8U=lsT1gq+-gmTpa1Pz(?8MvpHGhiiOE zes?2ZdGdCFjak#hx~xCOGSY{alm;W1@hid+tp;DMR%6E(f_dkqS1Ef$YE1YI_zyCe X9^6trG~2Nve^r3$?CEsNF(l(3%iCPK diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_vertical.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_l_vertical.png deleted file mode 100755 index 7b2e6aa84c83290f4af578aa979589783fdf1bdc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1405 zcmb7^`#aQm6vsbbGZ>d)gpA8F>)s5xq%l%v#wFwul9gmyu`@2M2u&ehvlXiid2$() z!K890qcMOMs+Py{G^*W-EOASz6NX@$vL`+5SHvS(4b*TUcw73KQ?=eI$mQ zeONEGquyeaPg^Qg2Fr6?f#lMVyEE0FIlNFjn>B$`gxzt$P~RqbVIa|74nj;bs`I)^ zuOF1#k|H{s>7D5R?LFAG1*#>-*ImGT>Xq-hydzsUNV9-^&=NF=PeqS{!4%6iJ|cKh z8IxnTvMJYNC>9~Z^1p>hS$kbHp(?{RmhkmbsHb2P9=xEl7uRP+ANf|h+yr) z%b&WVf{2=wpw*(`8&ky^m5p)wAZcI7&P@vA+v?TDS?-V_pj*uR6;QCvsV8+l2Z67n z;$G-h-3`a`hE`Vv#@*g7_jWr>#A)1sJ3KPk2*KQfAoS;-5ahh)P?)k>lfqj3IcSF z;A07|7V7+z${6_LV;5CNm#2XqIS%EIg~wiWnA%I{9`-d$W=0@TS-E3K?yy1kz1ndi zR*2+4(z#s*=^?;HXbvdDGa1VpjpeFt7#QlSNO?sHy-#6lYQZM74{b3z?~NYw8bMNd zs`ut}EJ?|?*#$-U^DJd$UM2KvYZoLi1ehnXV}a!J-_py0a}6k7LXf^({9DY)ob;j? z=?Ub|n3eTuJIYf+^L)Ppd_WWnZ0sdXgyu11H$?IDru;(C7KqqDf$SFEQ7E6WU8o01 z2JD1P!xnjI;?(2rXyk1L-l%m@RL`^xcn26eHPad!B<-gNd(_*4W^FdbcQ+bBF<; zON^Y9J290bBpYKJIE}E|3+N}TFkyJ)o&(G&cd`d&B~>4wuC5jyqJ%b|X|#f;n3js8 zBPlBp$FTO6B6*Hx;U}SJ`PivF1xrB5J zwfpT;WLPs6LkqGsLH(=2YwC3r;2oRbmc5*^nKcV@oy?Tx5w zm`H@aniw(0=Rj%6!8f=zd)5L$*;I|Tix6@mj1bNZGSHjlXW-X;u2lw>cb>3Qo1713 z!sdQc1VSMd{o%q7X0MD|g|BKRg~`eOh(JGdObD{*7FOYje^eUcN=ruiTwiNhfMPeL z-lbvxD4F*l8Owfnw%@-$ymgS;XR@4YUft8Ga5cpY^USso^E&US6F&ERg_0GfUa;LO;2QxOJ(=*9+lBGRz( zp4GeRuDSZl0dZ)6Hvg<1la0&c(}~sxRHC;WEZb?#A4yGRZ1b#Ie*J;Mc!%?)sGJ(x z2Evkb1`1cp+{1Inv9{#r$+AFfJ3or%WFlLOAVtJrZuWzAaTo&h(>-E$^o>YilE&|2 zv`ot8Ck_B_`1xsTCFM&=suW`-o-1zxWM8nzj<^}yFl|-RbI#_$O!eieiq4Lub?Gyn z8;zqH`w@xyESRm(6z?6sJ-jH>2K@?akNpSo9i2|4X0-yz9|geO#oM{g@g(aXMRbVD diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r.png deleted file mode 100755 index 852e140575cdb8919b6399c66c16909dd668b6bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1286 zcmb7EeKga182|1CW3+?{GlUW?Ekr^SHe4^e?Dj^6?m4?RG|f_^{Je&gx5Sp0MbYF$ zLLu!ZqaspW(WJ^RiqoclW>h?|shsJfG)t&ht6XU(Y9p7D(06GSUJ7==l10 z1*;hP7Z{YP&q@n_s0hvU2=D;lPCj-iQcb1pY5sKY)&El}lWN_dHEmust>pvGLyQeV4SFH4uD?i{mu&6x+f>=X2q1>U(?0bJz5sMu>}KD$}(R;4hB|4U-3x8&Dao#Rl8Dbtz!-ct9$S^~)G7 z8Z5Q_7?E`Hl_$0C*LTUkxhAVg=U47M#_zGxKYe*ZZtFsxKhq2Dq z0tq1tD7jS1eVa+-Z_;*F4fTy%jt;rOO*(~24oV^%u_$ldAleGuoT^i3OQY|GooeLB zs8L#c8wCjsFI=EVJ@)S|;vvgyG>x^%ig-P2^0ZlVCGOG?K=9o)#`Vej`}&{z7ub## zg+PmZ88fi#jG`Lx2@g-6ds;+5l^dF5e}Xkf5&A~J)ozjm^&nxFv{3c6jW2fNOGtcd z_DKjrOFc6`nW2_Q;g_9(lNcH(83whtf?H0I7h>XCdXYxtHShmk+4k2M+~P~X(S{#8 zAex$VV$cb?I4RYgCaN#NIX_SuTg1E|+_!~yZOhlU3Lq|=~o z*Qwwn8zeM0e82}ZmfTCcP(1a*>W~GI)Zay0Xcj%N)H`yhH}YDF}_>6G2C;ud_9wND~m^|C55|%rto~mef!*Yc=D($|0^Nv*u)%x#Tc8N zIN;HXhE;8yw^|I;u|Hgz`PcAHH=UCUNk_h;E2YTJ!7m@Z4gx6h* diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_down.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_down.png deleted file mode 100755 index 1930ed680138e4f769f3c7113f940c157dca49de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1384 zcmb7^c|6p47{|Y34%dva>lw%M^&dfM-gd81?H8yu8wUWy5bJe)1Eu=EUDz9;7 z&>)j@L@Fs`NTb+g6_s+$*x70S+JE0000>0gZ5q` zLO&z{7xizv!VM7+;p9VP02=e91;I$sZtUsi;~@M$B{GT56&R_jqDeN^%ia5oO@6Y+ za{Ug2pm)#9uwB4Mm76U3)az_d_KD|yDxHj;RYzahnZ6SI^>2^%do`!!PNeO;9$5R^ z6vJgE`!uihP~YMXoA@r_qW)LlTTQD}4yT{Wfr081(4 zsaO>z=f+5c4Wahl*!oq)I?J_A?`RvaY~CnIwk_-paHt3uLzp>Q3nTkn%KQ-gAaOh^ z3BkNX+zEoNQ)(6llIqSIY5)`p;xa*7jF%xAEzYt{dz1_R+&#skc@y9yR)M4yq}1do zW|B_5lxdec^87K zR}wmpl=WNFXM1mk_(n$VwjR&3(A>P{b}dw4V8m02IW?hF<#b#gqOQ{AFnZXsmeI$0 zHoh9P^x=wZ&vD~?cz@v-I=5t;o8@>Xz%qQcGOXoj=(A~>1)-Qx`taN}Z%>R%yvp+1 z?JowV`)p&;&sUs|BmxO2w%nl9yER&WldHvC*x03sZFA5$GL-TzdPeaZ^<8 z-w$I+Z+<#(3Q<|7w^8LsA2S@g9>Ncrgz7#kKbeVat0zMd(m$aOD zw*2z;fyCd><9-el>u4grWhRyyQKGun{;(muR6Ez@W2n|v!UByMqR;o%7(Mu_<@SIX?ME} zGt~GxMXs?n4NC@EEqQ8f!zjl0zZqR_jlOJwGAZ;-ukM-{5Yy9%MsXyk&r%xhci@2K zyV1D)qzJjnXjF`fI%8zrq|_sCO1tWzW^p>#AMmt0lx&0Lm>o+ns0h)#x#=Z7mom5? zCTjuJL-p{EbiD52BsH2;>rkekcP@_Urz}{H@NQlF;%_|oJcq~^BE5>!DEf}0%r$z`#M`n>OX$Ehd2NL diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_horizontal.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_horizontal.png deleted file mode 100755 index f7fa95fb6a575c20d4665c62fae109a9eab13f17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1324 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD8fByS;{rwDAxNg}3ZR+rJaSW-r^>+5# zZHu*d+LSd;r0@N|dhTZHTv1OI$Lephwmx|-=PjpLDcr=|A1_zHe22mO0562v&-p+> zYpS{PN{)B`8E3dn6;Dbr^}EkHYy> zgG%Su>;8Y{S$<_+azQk5BhRb}Rg7NWPcvP-bJFb%v&Ng*ce9tQXfx$v+xq>~rOKrH zTnAnS?lM?ce4xc$bTY5DO^Nlx7;dgdFJ`~}mb`ZU=T40R|KzSO`Z==Ko|gUl`R`p1 z-=8CiapuAWWY*v?V~{eRl-Uq|&*o2@TY zd+ZPA{J@!?vsO(c!Ph{$LA@t~?Z9hS*-s0OF@`ZMsGV_%fh9rlOwE=AMa~Al(h?pH z2A-e#&Wr~PFKuP@&i?1kz&B?Ccfv__C+UQFFKSJ6-!oXON<$Vli$uV_f zLB@t--AW90JEfv5?F$({*f||yycWgyVoKeFZUcrdmd)N9OEZi^Q*e!EG*Ey-VTajENg1?+R~aS7v+A?`71#cWEwTMiReH*rcD27hO?iIA_9; zR>NUulA*Ddrvk@(}< zRNs`X8EamgTXu2DE%O;A8Q)lsU6WyAFn@l_zHg7=k6&*;zo}xZTXbJJ;^Ma1EzybA zIbzBTaXWne-afPP_CKE~ z>^iD0*0PHhTAF(t$TCeSulu>(_P!Xym90~!-Ew!~{*l-rG9z-y%p0!n6)rGtG0ePq zt|P8gsETFHYb~#|kKD>3%QZ|FiIv5$WF6o6Sbk~H8vb>uD`rTrZ(6?Q>COi$7!1Q4 zKb~f}C2zW=)ZggS>D0sDQ=Mz4*4~ZgKT^>8Q@}qaZuaMIXFE^7p0`>mQ!0~djaB;o znI5;f>eHlsqW4;Ax|RM)dT>SX!rT}=D|v&-Ig{>78x^ZtpH`n}P*wIyY>K2%!}>YA z`3=$z3p$=@GyI#x^n~TW5%wATm=65!dJ=7`blit?5%bKQ~H X9K5yVheiUhykhWl^>bP0l+XkKqN-ux diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_left.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_left.png deleted file mode 100755 index 0d6b849e5e81c439f64aac9b0128d9249f1dd79b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1317 zcmb7E`BPJ86#ZWE0$~fXYfuuTAX`8pB7++{iDCi)r&z57K?wqa2qBWHk3~%t5U7l7 zlA4GRL#$YFL1b~E;Kou=5CX9TQ2_%OQg)io^dIP*x%ZqibMBnq&d))#06lHIHUL16 zO7Uf=5&3tqX!TXf^5@h5M|PJ>RLKU|<=CblNLg3k%)&n2m>| zTi@Bgssz}#nyXtu*VpWqFSejRr9>YTpSD7xN>_4-LojoE2WQ_m9FnztkU*BzaJ%9W zEaud~(0;hhS3d}XNYCK1H~@Zce$JqIBA#1HkBOffNl!A!XGO) z{uKJyVbPd(bCieb;luM*TmxEj91qZ!PM9UU8VD`Be{_VbC#z_TUO=0))$J`WvIUY$z|aoNpBK#MF;Ie`Q2qAtBSeWI8DUo?p0**|Hd zZJ`gRH$E3GCY#G3_<}RRu>@$j3R2f2&{yuU`HvlxZ4kDX(gKz1zs5_m4x}}oL&Lqi zJ}w9^G3l%&pCwR@i9~-GlFbRs6NR>GsSyC$bjS|$v=^Gfj;k=`UnW;G!R6z52H-;e zHPYGS!GT!=OlN5;a~H?Z)fz(qbQqbA_t9WlCh2V$hdg1HjW=lG1cw6G z(Exie5WtHCzP1&*ExF9?B)tQ3n9v-Y(Z+o+Y%yH)Gy4(Bw0*(vgRi6x{XUxzzKb zTUXsjy)zGMr9?6fL>r>McYU5L4ta|2`;Kw(#&~}C_2fu5(Tjz%V=9bm*HNE&*F8wQ zgifv-$#E)D6$jr24Z@uutW8y#MakG?jQn~pM20Dvi1m=Y`Te4GbNmDGQen51y*AkD zW+2&Ln0U!w8C$v;*B@RpjR>-yvAJR+dNEOpA-2?y^O3qu%W0zOSO-;}`6?1sVS>ik zL^DzPX=vd-JvSRt-!KS=DGSt(Z`@`Z(n9W(;gx;?BCBVKNhe>XvRXBNY2}+6p4jlPgXb8bEAM_? zmFy0(pBzU8f_aIqpOOG!6?UKn(p}f|6O1gA3Pz^ZAUQ>bVVY@!#m@D8P%p@`DIDH0 zb)+4=gzBsWB~owxEZuyby?s$qoabM@d42`M=ncs!kmP0Rm#jp%{MR5%R3H+blHa_` TE;pxIT~vVTNAtbq&CK`*JA-Cm diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_press.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_press.png deleted file mode 100755 index faed4c47ff53a9fbc5446df2645d3347bb4ab70d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1364 zcmb7^`#;lr9LGO1%O%WA5p8q7G}kza5?z`K2 zpPbOYQijX>SALm94#il{J)QuxUs^AXLdbUqvOi^)?EjS9BtPH4&R>yFnwk3of>Hu6 z;oRxHwk*+M?sOdz5J{&v@=v#Yy*(*8JYkKX{OT4pXzyPNo2F#(+$^F0wrEK4ctA2k zT6^b(d1v|d_X7H%_pls*h3H6wO?CQ%`nm2L=A*<%7|u7CR0vMorYb4tN@lX4X5{Tj z5n`g*X**h}13wptXcT%$GRjh)&arxRiqU57@36(fh59|8jd7C+A^=c_sZ%C7gLFi( z1wR^8^$=3eN1Dqvl>gGD34%?=OewNm_2Dx{;5C*p=SI)4V7y61K*gPO9%k_Df-8sD z{>`p5@SHjv=p)1QS|f>{nMl8yzS-d`umcNA#@0L7PjEX@*Ltm%?Zq+Jdezl5_{skI zYiF@gTff7|A6QQ=Y;njoSScRA7RUaP zY6I2&;hu&?o3{f!d)$meZfp1DXF$syTM;TqzF%lh+6p;$sZ03es9Aykj<7?$zUO4#IQ|G3P5v9`qauYX0Xj~cuoL`v(2)&rK}PTg)Pg+Ot)x$p z&KuVs1=Bx?zTa(ZfM@$JWkUJb_rQSfq72m+F$eGt0shGLUNOx|1HY&DzMNO1o2#x|X6?l)w0x~J&0=zxXn%9Jy<(ZAg zOB(iL$u*SIS*KX&z$)ve`tKA)$*#EF^XqOn zJL)Gq6KAxn$BRFfRIwC;f2nRWYC8~nksABv>qn-GnZDZaSk;%EbV}bI<33#$_XW;u zB(1#Nd`$ECa83E#y*jkW^7!e>0Gq|N$132}_)0RZf6~{+(F$*6n!a$1;<6d|%!q+2 z_)6S_vY%2YZL6c diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_right.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_right.png deleted file mode 100755 index 0473f3ac6a473077ff8a4dd2cbc0379af72e8c08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1321 zcmb7^|3A}v6vyBDjLm0^;X#`(-Hj`^d|RZrbG0$#`+T{KL{sUiL@I7|uYFJ ztE;*_qBXJW`ck$`CMjPZ`I@1aZmua3+vTqNAKdeJobx*8@i?!?`RP1!SOH#moDmKH zfcM_T3|1-fB{k5hx+uN0q7t=(41WdyHTjxKj=E}gVEKi5tpA@-nN;frf{ah9j&LG4 zAS5w=CpYk`2lFp=#(%YoX#pC^Ga0H&=e(JW&>x5A%4YMX$XbXqbk?dvv2)I+Jl$DFy{nLpA*{-XkuF;NYOfh~F&4#K{$!`NKtOuirQPpfB->^_V270Nys({) zTuhttr9|sGJ2{S`*?z~4Mn~Ao-3;Rz(EP>sYn{^L(^PFjyR}vHFHg?~fm4tC8L5dS zBiZ|a(oFfDNbyOFL=`uLMPISZ15q!yC5D4>2neyDVPG2B5s9*GTYDQ$|!-~pjJNKpuQPT zE^e7DC`)(O*QfyKcNZlkL!SNS>0md7+q86ka_+Y?n5CTRL+SIHb!bvYsh%{N1SPE) zyBKI`iSHv#TiJsoVwfP~XV{0Y!v*kU8II6|fyjggEn z=ro>{u)UcQv6(=Ile9F^w&6KA;XK2JdPxKe9=8Y!4psDQ z@rWP8HD#(pavCfQ9Z-B*Oi8V{S@e8<8*(|Vo`>Q;NiDe5CR;NAF~_$KMai{$y+9)u zPU02bS`zi6DrkSYxXFXz))3#4#vuU?jEe3dL}NIx)}h8>c4W)qz+rsmb>=OX01FoB z?wdGC6ARV(#KJeMsw+EXz|h6DrEjZuUuko*zPE+Z*g!2u@XjwHhI1!kgG4>yOm3)F zYMEVoRG;TmXJ2B~9GWFTFS`Wy&zW%!eLoPpwJJ8bs$U-s5i5UaTo?w$$Vv2dU?hb4I6fn~*@D!3lWRi6Dz!|oZ#KYAbtzly_^=f6m~ z^lXtgsE~aU>tsJxXWch!9BdixezhbIcW}+Cc3f3Jq7#LcM@n-<=cetf|AUDB-Y-Ov YW2=fqv|cebLPx# diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_up.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_up.png deleted file mode 100755 index 98d7d1e4a948176cd0b4fe64a3fe91bfdf95a710..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1370 zcmb7^|34FW9LGP~e3@m&%9pfV7S<^ZMJr)*H<=pwvV^W$oNp`A=}^R?YtEONiM}kY zICqrq5oXO)L~g!+SqO(%Ov$&@mQME{-0Sgpzn|~-HDr!}YqJGwdoP=0Nl@}%n8zRR6Pcl!E&wDz=iu3MG3yf$f{^K|n% zbL|kRJz;Zcy~W6h_b5KFWoo;uIX!lvIMQPVW_dFec}AKn9Zo9r%@=-D`&)so6&dFQp@NHz6U70T@?HtrB32^cEiy zDuP2Ii%T*_4v7ie5m#{ucjTa$kU8Sg0b^h_N9Dobz`O!3{cXMuF3p;cg*OAdH~ai1 z*1+Ag^m5mS1huk;Ft0j6T=hu-!$QTqUHov~0G?idwv&`pDS(UFN&H)b^PA&Z*DG~_ z|0{g|B0W&&8E}x7vWNYd#Osq=tU(d2FS=o}-u6>Qr7=*3ETEk@gn{>>(J#QaCvw-z zl3qF7gZocK+X+?e+*tuRHVI*g9d?Ly|8Rb1lM?P1ze?NHPM2ssv|Iw1;e>hh(xL`S zahL0qO*qnE|JS!s-ZXE_*=ZI^Pv{Au%m+NFcOVusOrkxuUG(Vy=p)=1GQrqi-go;! zgBhBoqS-v1jPV3l%*_D#)zq!j6|4K}sM=&mld*BA@Rvu=Z|WL~?+icZQ87*YN=6x& zX;~GfO?*IL1Oj~=%alKE{4j2aRXa!uyJ5jG9DL>Mgmp4YO~jMV{Uekl_4l? zBZJp7my4!_tBN#?c77?8LSt&}LyGA^&bbq$sok&1aDQv$Xn-wLh8lSQ3lH+7o2gdw0`je}?&Us&lzGPVrp)TZbx z!~PBd$3hJ>fsZ*Da;SO@#ykeuRn}_OkWN(D7RaAW#m!8}@s|r)>WowC%6Mo)GR(}c z1y#V+TG{JMhSfYcznLGXij_rjUcgKi<3j<}N8QDl9SttuPHnlX`YUI5iOqKme&Z3p ziH+coB)TZ2v5S@E)oLB>UV#8I^0qQ*7<=5lJTi3kN~z>hwr52q%+_>YRklGsfu8?; zr&|l9yx6~7w}N+|>N~vIQr56sz?hgRQtqwkit>w>QSNv(2l1ZI4P54W-fkemVkCyx z2&&FKbL`8M#_E^D{ckJjn0xk}Owh79i;fnuy=uSIfR& zU6eCO9o%suy9;(YlPOPuKf{zJg$eSdk$FN9Vaj<2INMg->?~w;qgT9l^f0wIe1J*| zFLi8R=Ct>&1ng_}*w8kFICv@}T+fc#y@XbcW|Z$wtd2F(qI*wgNa)KSg_T=ZOn6^n zmG=)9U{hqin6xg09uqm+5qVmFEYNrn`_mOv^H%KZ<*V=V1}EN$#UVqukQ$%Q9!Q%s z>x)V|eEml1pktF(!O76rGqmfxWBeF)i?L611g>8l1};AcA&RzJ?6lXRhMV2{k%;TQ z546!QHjS!@NeAYVTaSXLHX39S>nW_D8eCtXpHs7ik7nQ;6-0=Ugw^M6q?a>`&&q4t zBVE|@#G#7wzGmq7J3RtcU#SpTfE&0txgs(6Y1=t&o`HliMfuntMHC|=U_;ER*{1@GiiuwQm diff --git a/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_vertical.png b/arcade/resources/assets/input_prompt/xbox/xbox_stick_r_vertical.png deleted file mode 100755 index 5d2cfe94bb206e2eba7eccd410911a2d46f12c35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1465 zcmb7E`#;lr9R7S8GYm^*a@&-OoZP8{Vj`{P5;n4Abiv$CSxk(txkheXmc+3x%0f1* zD3^JmagH68N~9<$p_FQ*)y~eJaNe)i`+45a>-9XpJU_fo`FeY5sOhNz05piZ-Tf2{ z|F4g_epirE1i^~{fS_1i7N)Gww!WkQ5BdKYMMz;^BV$Dsn44tpi0z>JM-2f=BA-WR+n4=#q#TH+~qv3sT$h0MSr!?_h+sf$6%9Sq;*rbje zo_ydr>PEfElr3Ozuw38O{80F^2VB;8(NO78996Cx$wed*4b&P-RZ5cl-Co*2?!C+=!w=G6LeX#ed^R;hYz|2T`F`FYVp;nZ91Z86m ztutEWF~(Ya@6T_~EUqW*G{$BCen(KKxG(DGq{UW(rw{3zo7vwC5` z&IcjD$Qoa3ldz;}eRv!JUtxbu#r!oV1cZqrdj+0Z4Evrf6hO%eRz|FAX~ z#9||n`3UYS_Q(Llq|y1lymAQ7?cZhuIE~v{oY~N)_&-ayL=4bNj`KgMM)q-rOd_G{ zntU(%VX`VRn6M7o$aSeSgNJYD)}Kcv+LZ!z-zpl2u`Dl9QMS64t>nvH=4}J5{h z*5@l(p>fjyPNVIH!MXDdJUn7Z%>*!-QzfW`*nijv20AWB5D3P^oC9&J)7^7D_72;ygAjMF5gY zb!ufTFQX=xb7YRoS_K|v3`aKZ#fejBSj{7A6XS}`om2BEnk$(L2rlFoyWz?8YBWbGb8%hYmm=%Wdxb$f!eX9y<}blj zO2vWpF>SB*bkT3$tQwEMqwqM->4KfjNw0aq6afpXV;-`quaJ~APFoigsAyu;KS5qi z?zyp%y`mV=QyP6Q?`Gg_$Ye%*Z0@zqm5b8RGsU5w#VNPqUSyhx%&FahcRc1r#^pg~ z-&F!PoJ8f#{^%x4%%4_17fc%$TE7oh>1A(7nbsioXofWopNV_LFY<)@lNhF5@2{9G zLo-TwGEtQMl<>3Z8Bp5rkv^F`)77l{&w`|~kmQpU)T!k>Zn-PbVC3Nd^3d$h*icdj zxvx!QqWNVW<)u16S=BOaMc&-}U-@O}n!ss1JZ&o+t?h1uXhCDB5vc}}O-_KZoysQG zq^Sev>_s9T%@cnTN?Hzs;p3ZXu`d!;lmDqgSA8|6NpdH5|BZvw>#dc#5Ywbo+luB Lc)Q+Wc( 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 Date: Sun, 9 Mar 2025 21:06:57 +0100 Subject: [PATCH 17/34] add missing resources --- .../assets/input_prompt/xbox/button_a.png | Bin 0 -> 982 bytes .../input_prompt/xbox/button_a_outline.png | Bin 0 -> 1269 bytes .../assets/input_prompt/xbox/button_b.png | Bin 0 -> 900 bytes .../input_prompt/xbox/button_b_outline.png | Bin 0 -> 1196 bytes .../assets/input_prompt/xbox/button_back.png | Bin 0 -> 860 bytes .../input_prompt/xbox/button_back_icon.png | Bin 0 -> 673 bytes .../xbox/button_back_icon_outline.png | Bin 0 -> 882 bytes .../input_prompt/xbox/button_back_outline.png | Bin 0 -> 1059 bytes .../assets/input_prompt/xbox/button_color_a.png | Bin 0 -> 982 bytes .../input_prompt/xbox/button_color_a_outline.png | Bin 0 -> 1269 bytes .../assets/input_prompt/xbox/button_color_b.png | Bin 0 -> 900 bytes .../input_prompt/xbox/button_color_b_outline.png | Bin 0 -> 1191 bytes .../assets/input_prompt/xbox/button_color_x.png | Bin 0 -> 1036 bytes .../input_prompt/xbox/button_color_x_outline.png | Bin 0 -> 1336 bytes .../assets/input_prompt/xbox/button_color_y.png | Bin 0 -> 941 bytes .../input_prompt/xbox/button_color_y_outline.png | Bin 0 -> 1253 bytes .../assets/input_prompt/xbox/button_menu.png | Bin 0 -> 774 bytes .../input_prompt/xbox/button_menu_outline.png | Bin 0 -> 1086 bytes .../assets/input_prompt/xbox/button_share.png | Bin 0 -> 658 bytes .../input_prompt/xbox/button_share_outline.png | Bin 0 -> 880 bytes .../assets/input_prompt/xbox/button_start.png | Bin 0 -> 879 bytes .../input_prompt/xbox/button_start_icon.png | Bin 0 -> 666 bytes .../xbox/button_start_icon_outline.png | Bin 0 -> 885 bytes .../input_prompt/xbox/button_start_outline.png | Bin 0 -> 1077 bytes .../assets/input_prompt/xbox/button_view.png | Bin 0 -> 846 bytes .../input_prompt/xbox/button_view_outline.png | Bin 0 -> 1168 bytes .../assets/input_prompt/xbox/button_x.png | Bin 0 -> 1037 bytes .../input_prompt/xbox/button_x_outline.png | Bin 0 -> 1336 bytes .../assets/input_prompt/xbox/button_y.png | Bin 0 -> 941 bytes .../input_prompt/xbox/button_y_outline.png | Bin 0 -> 1253 bytes .../resources/assets/input_prompt/xbox/dpad.png | Bin 0 -> 351 bytes .../assets/input_prompt/xbox/dpad_all.png | Bin 0 -> 351 bytes .../assets/input_prompt/xbox/dpad_down.png | Bin 0 -> 416 bytes .../input_prompt/xbox/dpad_down_outline.png | Bin 0 -> 400 bytes .../assets/input_prompt/xbox/dpad_horizontal.png | Bin 0 -> 435 bytes .../xbox/dpad_horizontal_outline.png | Bin 0 -> 377 bytes .../assets/input_prompt/xbox/dpad_left.png | Bin 0 -> 434 bytes .../input_prompt/xbox/dpad_left_outline.png | Bin 0 -> 389 bytes .../assets/input_prompt/xbox/dpad_none.png | Bin 0 -> 398 bytes .../assets/input_prompt/xbox/dpad_right.png | Bin 0 -> 427 bytes .../input_prompt/xbox/dpad_right_outline.png | Bin 0 -> 391 bytes .../assets/input_prompt/xbox/dpad_round.png | Bin 0 -> 1032 bytes .../assets/input_prompt/xbox/dpad_round_all.png | Bin 0 -> 1111 bytes .../assets/input_prompt/xbox/dpad_round_down.png | Bin 0 -> 1112 bytes .../input_prompt/xbox/dpad_round_horizontal.png | Bin 0 -> 1118 bytes .../assets/input_prompt/xbox/dpad_round_left.png | Bin 0 -> 1125 bytes .../input_prompt/xbox/dpad_round_right.png | Bin 0 -> 1117 bytes .../assets/input_prompt/xbox/dpad_round_up.png | Bin 0 -> 1128 bytes .../input_prompt/xbox/dpad_round_vertical.png | Bin 0 -> 1128 bytes .../assets/input_prompt/xbox/dpad_up.png | Bin 0 -> 436 bytes .../assets/input_prompt/xbox/dpad_up_outline.png | Bin 0 -> 394 bytes .../assets/input_prompt/xbox/dpad_vertical.png | Bin 0 -> 422 bytes .../input_prompt/xbox/dpad_vertical_outline.png | Bin 0 -> 376 bytes .../resources/assets/input_prompt/xbox/guide.png | Bin 0 -> 1193 bytes .../assets/input_prompt/xbox/guide_outline.png | Bin 0 -> 1597 bytes arcade/resources/assets/input_prompt/xbox/lb.png | Bin 0 -> 589 bytes .../assets/input_prompt/xbox/lb_outline.png | Bin 0 -> 718 bytes arcade/resources/assets/input_prompt/xbox/ls.png | Bin 0 -> 971 bytes .../assets/input_prompt/xbox/ls_outline.png | Bin 0 -> 1280 bytes arcade/resources/assets/input_prompt/xbox/lt.png | Bin 0 -> 533 bytes .../assets/input_prompt/xbox/lt_outline.png | Bin 0 -> 722 bytes arcade/resources/assets/input_prompt/xbox/rb.png | Bin 0 -> 684 bytes .../assets/input_prompt/xbox/rb_outline.png | Bin 0 -> 800 bytes arcade/resources/assets/input_prompt/xbox/rs.png | Bin 0 -> 1065 bytes .../assets/input_prompt/xbox/rs_outline.png | Bin 0 -> 1354 bytes arcade/resources/assets/input_prompt/xbox/rt.png | Bin 0 -> 673 bytes .../assets/input_prompt/xbox/rt_outline.png | Bin 0 -> 824 bytes .../assets/input_prompt/xbox/stick_l.png | Bin 0 -> 1228 bytes .../assets/input_prompt/xbox/stick_l_down.png | Bin 0 -> 1329 bytes .../input_prompt/xbox/stick_l_horizontal.png | Bin 0 -> 1257 bytes .../assets/input_prompt/xbox/stick_l_left.png | Bin 0 -> 1263 bytes .../assets/input_prompt/xbox/stick_l_press.png | Bin 0 -> 1309 bytes .../assets/input_prompt/xbox/stick_l_right.png | Bin 0 -> 1248 bytes .../assets/input_prompt/xbox/stick_l_up.png | Bin 0 -> 1319 bytes .../input_prompt/xbox/stick_l_vertical.png | Bin 0 -> 1405 bytes .../assets/input_prompt/xbox/stick_r.png | Bin 0 -> 1286 bytes .../assets/input_prompt/xbox/stick_r_down.png | Bin 0 -> 1384 bytes .../input_prompt/xbox/stick_r_horizontal.png | Bin 0 -> 1324 bytes .../assets/input_prompt/xbox/stick_r_left.png | Bin 0 -> 1317 bytes .../assets/input_prompt/xbox/stick_r_press.png | Bin 0 -> 1364 bytes .../assets/input_prompt/xbox/stick_r_right.png | Bin 0 -> 1321 bytes .../assets/input_prompt/xbox/stick_r_up.png | Bin 0 -> 1370 bytes .../input_prompt/xbox/stick_r_vertical.png | Bin 0 -> 1465 bytes .../assets/input_prompt/xbox/stick_side_l.png | Bin 0 -> 565 bytes .../assets/input_prompt/xbox/stick_side_r.png | Bin 0 -> 654 bytes .../assets/input_prompt/xbox/stick_top_l.png | Bin 0 -> 1268 bytes .../assets/input_prompt/xbox/stick_top_r.png | Bin 0 -> 1359 bytes 87 files changed, 0 insertions(+), 0 deletions(-) create mode 100755 arcade/resources/assets/input_prompt/xbox/button_a.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_a_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_b.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_b_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_back.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_back_icon.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_back_icon_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_back_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_color_a.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_color_a_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_color_b.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_color_b_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_color_x.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_color_x_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_color_y.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_color_y_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_menu.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_menu_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_share.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_share_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_start.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_start_icon.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_start_icon_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_start_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_view.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_view_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_x.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_x_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_y.png create mode 100755 arcade/resources/assets/input_prompt/xbox/button_y_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_all.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_down.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_down_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_horizontal.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_horizontal_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_left.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_left_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_none.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_right.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_right_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_round.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_round_all.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_round_down.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_round_horizontal.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_round_left.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_round_right.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_round_up.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_round_vertical.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_up.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_up_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_vertical.png create mode 100755 arcade/resources/assets/input_prompt/xbox/dpad_vertical_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/guide.png create mode 100755 arcade/resources/assets/input_prompt/xbox/guide_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/lb.png create mode 100755 arcade/resources/assets/input_prompt/xbox/lb_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/ls.png create mode 100755 arcade/resources/assets/input_prompt/xbox/ls_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/lt.png create mode 100755 arcade/resources/assets/input_prompt/xbox/lt_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/rb.png create mode 100755 arcade/resources/assets/input_prompt/xbox/rb_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/rs.png create mode 100755 arcade/resources/assets/input_prompt/xbox/rs_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/rt.png create mode 100755 arcade/resources/assets/input_prompt/xbox/rt_outline.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_l.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_l_down.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_l_horizontal.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_l_left.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_l_press.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_l_right.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_l_up.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_l_vertical.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_r.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_r_down.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_r_horizontal.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_r_left.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_r_press.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_r_right.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_r_up.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_r_vertical.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_side_l.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_side_r.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_top_l.png create mode 100755 arcade/resources/assets/input_prompt/xbox/stick_top_r.png diff --git a/arcade/resources/assets/input_prompt/xbox/button_a.png b/arcade/resources/assets/input_prompt/xbox/button_a.png new file mode 100755 index 0000000000000000000000000000000000000000..2399fc263be2c40edfe062170cfdfa21370876f6 GIT binary patch literal 982 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDvQL(vI4BQEfIt{EJ z<}<0(?~<`o-hFwwJo5#;_@`>?-h8QHn4i4T<$`Df)xzmE{8WV?BZxwW1M2r z@Q5KskpDP?M_nt}kIpyN-x121< zXJYYc`h8uy_P#*{W5*O0dGVVb=jU%{n8I>5IqLkn!W--p zw%sY*tY9v!#4hZwbEN#!sm1P_Up>51zrWN~EcHb})bEyv-A5R|wcYr5hvE3d-S$lG zB5#@&O==GL-TzIHbDU{V(; zo3=&18x!Pm*B=4pucw7gRP zkGIZX(ODD(ubJ3l<|GL__D_qfYIHT9MZ(r3| zU>hbAjI6}Xk}&H8&N i36$P9Fn|;Peum_{^Xu$%m(2j?ECx?kKbLh*2~7aTJiwm- literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/button_a_outline.png b/arcade/resources/assets/input_prompt/xbox/button_a_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..8dd7cb9f77034b57f8b1695c3b178f4173163f1e GIT binary patch literal 1269 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDpk*#bgXQrNYTiBtd#Qw#SOFeRS067-Sd4VT>iPc)1!K4mZ2BK~ z_NuR|2Rln|{lU|JD||Z|m<75`V@tl4ZHzDEO_+J`>PK^?Jl4+)-)!r6K0g(kX7`4b z;r^!o$3wq-WjM`v#pEBq<4cD{hxm`)x#5ziWZAI2rlk7L-)}t5vJFq_L$9)x33qI6 z5M#EO>2yq}qnI)4-6fIdB9j;vrdUsXn|qOwtsqwCLrM@IE5{7=_i3$!F?FGTzUWfZ6@i>l-KPSUhgL92DDtj)`{=oYlCZ3azL^(e_5#%|^ z<(R2q8{T?t!4`pOuZ0@&_)e!@6-48JxQH5EpdfsfPv<$uU3b0ii6rhHD^7|J{{{m zBPPPh&dgqY)yYg30nZ zd1uvbC)ZdPT6?WIUnpy?@OWv*@^>GS7W}w+W@qfh#~0uAFHrvf%eD2!=C4=NDtDb> zn;A0c+y#{*Pd}RJr)#Z?%Y z#$2lOV9qeweV3!{i(=u0?Mps?-x(Hug=_v?(bT=i79alIm9cHrhRXPLGxJN2UY)x1 z?liW#FO6E~-~4*?Z9$A$u=w0n{14Vm{chj#MZW7pTvvAB;lKRd(ht~nFy=LYsXE5Q X2Rm5AnP+eU3myhfS3j3^P6zanPW!H8vaQ5mNGKOV0Ol7}t z;f@shfln+veoh~%m{#;3if-u86FbT9fSDnr;ULo<-5Fkat1~#6{&Upm-QM*;VGpxK z3xo5QW;TPWR#C|mHO2;}gb&FIj51OUyNtg-5M8igV5w~hA9kxPWm0mfR|BB^MDsqL>oh_ zRLe|}mRgPtry2ImR(!ap*o4L5%bt$7`dq$%4@nJmh77+|H?lRXW_(pw%99h$+@mM7 zeD((CC9R3ycXv&`7|L_$_=?G9g8%+LxA@JJvVY1N>7^-`mVBA`DSL8IWK`ek*r;{7 zKD)LV1WrryjGgte>+)w-1<~cZKJAg5@_ZKC(X@*T0=8rvSmJ8@>&){X(xRpht}olY z+^I4qIPi8!{2EW&yJAJlO@1<{RBhf>{o{R4-cIlBRz+-L=GFhruN_(P?%Kcar)Ep0 zW;dLXy?wVXmD!IW$2@WUpOcxJWbUe+{9yH){XjxZzL`x<5M%Sj4_r@#_zJ$;3vD#H z|5g1&#TBDT6Q^Wqq};0c8@wc@OFGkhLebs#E<0!RDlAui$$3!uPh`_EB|(!*U$&-r zs{j6JJhdcf(!b{{Gjs~mPE>JQhQ8$~HTJ#{<8kqXaJcc(yQVzno=DA5D&M!mHt&OC zsAWR;oAW21{MFq4PegF<_Bk%|XZ=q!L`tUi(jP8(+s@hm%0>*Hu6{1-oD!M<+d`uD literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/button_b_outline.png b/arcade/resources/assets/input_prompt/xbox/button_b_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..474d894f8b2976f5fd2eb083e9e9ea1c98086706 GIT binary patch literal 1196 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD+5n zO^X!-9HIps9lreEUuI;U<&&hZvvcOkrt2$Y7L;3UsHt>d&S2nOz-ZFIc7fqvFoXDk zA3r7TITY+?GmwlJmd9c{=lq_LT14wbdCtnyeOPB|b)l4=1q3vpu*x{r@h`eJly~ z|5r?ZvT(5npMyz#+w|R)zJiRb3(9g&m3+;+9zT_R!+M4Cf9#A~n=Kk{KkF|$DOdKt z?KHy-?{oi4#h;fn++`3wTd(|pN60{b)zSiuG)0L6YIe1iclK7c>G2-8kgwX?XT!3< zWPuCgy%5HWEDK^9;#TqaTDE#KoPO!|=!VIyF2)*B-kM-debxz#Yh+F?ii$)}8Kn1y2R)blbi9F|m6Gn}W9oWjEJUA>XL;+9!y%fk+4)|A+W zntCs03)cn4E7@%rzA3O5xX5Y$ymf>r;~IaWupz_etwjg7@_hTs_$ynphFQWw@`&0j z35Fflf_B_nIf17jW$}%dt*_(nGaR#N$V|CXz$_8T5XNc5EBNi{D~8XXQX4*`GHkAq zbf}kZIFVZC$yOuFFnfL5w7wqu6wYQjOT#HqhjN1Ngc=>SyDrH((fHH&~plEs5 z=i(gp1P|3IOP_arbhq4hVVQ^qmzT*dugB;ON>u|ebI)zOQ%X^ zpIaBIb#%EX?<235D!YqC_*Z@63Mn+dGSfc5k7yB>2j0xmkwb^fqr@}ia6 zcO){Z%ni4%&WgG9m!o7!{{Ddd_ov54|MLh=$=lx`Ja?Uk!1n}M@$`i6>slNNDt%hw zsvqW`nby2G&p9Gfi`g)HrivUdONcTjhslmDjg4E>Hgs?ptt+U$_$NlVTQff?QL54* z?f8+kiMtv4FNLXXo9H>+)M(?jm{}drQ$=FaG-0iCfQnyHcsKF>z6y zlRnSiymWgP&%Iir53WdkyKLEQuwr>%bmHfkJC=T4s&vQMVs0Dnh4bhAujIb0Ikn%_ zcWu(`nHR1}r@j1A>|`?g%8i=5>nfiPFG;Z4xb?Q<@hR*I7t@xTZ;!6Md;8z^-LcEr y3o76Koow{^&z(bwA#qnO{7byZAO+9U_H(}PN?DjPV=u70VDNPHb6Mw<&;$VL@Fx@i literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/button_back.png b/arcade/resources/assets/input_prompt/xbox/button_back.png new file mode 100755 index 0000000000000000000000000000000000000000..3318c6a5adfc5f8eaaa6d57f997d43d3fda034ed GIT binary patch literal 860 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDEaktaqI1@ zmy?>TJ0Uu zH+ekgo{-FR!LxwTL~XK39m7tU2W5@S4l{EW?O`afV|dGaz}M}e9K-TI!G8W%w?@SV zfBUNNKf83j_qpYFc0MUBJr!ch;AgzySb)abR@e1&eqGov8@=J}SD85se&=UA=KQ#S z+QF6m^}5{W7TjlJeWB0rjN_uL)3dbV!pVQ8Lz+`YB*?efV*Te$UV->ttR z?RA&QgC*g9mi62jeU=Ha`W6nIj8pU`JSCcI~U~@{f;UL2`%>xWxOHYNe>#XKu zDbU#zT>WbOWac;9Zt6Dt-m1j4z?Qd=b-^X(Gs(Z09K0F~H|gs+Fs{mayFO6-4pT^6 z;Q`hQx#F4qI~H*9yKFCH*fvq1xRHnNXoXC}&j}gd7D^O0dKE~>GX7AUIn&-f=7PKI z@pl#hvSJsGB?}bD%zu16DoJqa&0~L-#$J|*zOZqA_Vsy}6g6kAE$Lhux0K<^wymxb zFV6n^&pPLL(*6lk>TGrYZ>V87!|m{dS)fjT`5RYu3&tbed(?9>+>-x$|nMK}Md@hcCDm`0GWX`wlWG0|lXkZ_kz~!L-O>7@` zWO!Z_5hzwK{36rKU^v;xqD3(wT6*Q;wYMW3>?ZzmJh7Z%5X%P@om-u+ya7a6`@$07L1{qlQS4;UtC+4DPeGQL># zXZIiB_YA&M=W}KxJ6w30&ZNP^7&(1D_X+0%t4^P1UE}N!x=t;x*M`OC)YUUAKF$qB z_Tmm|4T~rLUEEM$su0H;v5rkfG(4>#QZwP%{IzC_9&HmW-2Uj%R?*G;iyxIpeC&Vx zs91)ZYto~j2_9aX9tBPE?7H+Qa7x#yPmcnoyE-u=7ZogOb*1P{X zWlUE8cK`JM`a1JBV$gTe~DWM4fZEH8w literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/button_back_icon_outline.png b/arcade/resources/assets/input_prompt/xbox/button_back_icon_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..a9ee088896fda59792127c27a7c3ec7e4ed52c5b GIT binary patch literal 882 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDrIOlcp4mC^_V{W^WR;)=So@s1eO9T>&buIKABFCUKz1pr-@Ob0So#k&5-N%Uzu}4 z?!}Cfa0c6UmuUvC{TQnJrZzb;FwW^(8MUZrEkkkhFW$sFrVDna-PgY|JbU}xef_kT z>mAhoMjEd!|B#WzI4xUv>B@h(N56gzVmFABOU-ub*;`Y-hOxqO{`Reb=l1y}En}YZ zt>4>wL9OLQCsvEE-nYeGsdp$aC-lGH6D{q^->$05dZ6@j{aaC4#s>up7dHPpJ&TFI zAcownh`35J9YnGvD+8?HsWSF6CwZS3cv90-w%}D%`I5+s-5vzwY$jo1#PS3*$lof`^&1Z zy;L~Vo@c{xB~GRV+kUT=FU)>_J(O|Am(+%bw;4X&W_bLWfp7Bd3n8L`!<)GfgdF`*FXjMPY%{1pH;pN$V@vJTmWyg0t z|9oUy;+gX3MHBLz^&1uztXTBLb-8#`K-L1+l~aAZ-T%(CRy~!P{#$d$<_423?WZ13 zRpPwvxnF7DlsLgCmwh70+J z*PUuS`uv33D%M)*H;YpGC!YO%WMN`P=rgwCWov)kK34Yn_><*M4>E-xS>~;B;}G6s zT~rZUC|Pul}e>S|B^ z*wZ`5qduEWZSMXn!MXljJ zeAAfD%%gjMzhDoiaYGB!vM|T?tt%D?DZFcp*uA0s_+LR6)(cyC_N0WcFfzP8@_ZVD ziq;i|2Em35>FQb4j?)=@*rsh`RWRJ$#<992ufpJ>UikIU-R=9lE9{uxyi$G;mn<+* z+r{L;oSW}@bk@YryS{sV#To1D1HaNv1iP@yF__M_{8q&(Hn-^Rk;JR#f4*P-_=WEI z+zhb>d(#IDGn(H#;ZQQ!u78bx_4J1a7!I{>sAtG2`d4cpXC?Gmhp*G_d75qgiHp0; zr?F0$w^eRY$^6^rv`oLVCiFU_F}j`SzS--^aiq2{%<1k&XvT_ zGCL$^es=twf9LcWnB1P(?)|#T^1#~}sf;%+$tjm|8A z3|{BIo)T}=VtJ8t{KGdMMvm(qGu4|cWu8s3UbII3!aDUYt^uz)lGa(gW8V^a|9TSt;FLJGm9H({u)o;aV5hQduSIhhi;8>I&c2eu+J7JS2;DsS zH|B2cJ`077*LQz-6{5T%IWOqj&!~r zk$1_|lzHASy9t*frJE9cm+qcubhpI&Skwvow96ZvRI^0NgD6re{`Eo6NRJ-xl z8OzA8g?nzPLYsDUuUC00004XF*Lt006O% z3;baP0000pP)t-se6|36wg7y#0DZUsez*X8w*Y*&0DH9neYOC6w*Y*%0DZOqce?<6 zw*Y;&00000eYXJQXy*X{000nlQchFPkFPIJUvD4pf4@Hu-=6?3wDXMs00S~fL_t(| z+U=X$a@-&gM3F#3T+RP~ZJesu-p$I;4TDpuVqR^DBc{1duX_8(p%%abSO5!P0W5$8 z@Sg(2aJuY2BfB%i9|O=XU*#sQ?DTwqkW;Bd%3&seb=B7YFgQC2!0rW%-A(|&o97Tf zfVf?SiffCK045bWk`V)lxpYO2G5}VU&QMkY;3xxej03nb6mAqCm%+$m0K@G!3Dms& zV>(dN+iwum089}I(+8k0DS%7&07S9&-w9#|fG>jKc>q^L!?6Im2!|#Clr(_i09+9d z#{fuEnSk&;@E>2%H}eoC5>l4gehjpR$hV zK)?VDpHfsb1_D6e_HYgk{W4VD8Ay-Mr0yKN0t8lqj0^(+;8+PVGIRid?@A{q91j5W zbtam)Hh|_NXe}p0-2^x|32qxF!=nk{I0;@CCqvf+*eMAfU*CkuaBl*5N`m{>D?{Hf zcmYV11RodKM9-hM0F(sXV_MIyao7hCA3r)#G86&G&kMI>L9Rd*6HlmK}7U`B=l0QoiL0hgh`OjxOj1c8~*({mX$kG26aD#6=0(fkc8$JIOd zrVQK5v0*=f>p(NSC2N%BS~Aq5o6^*}eov#D(#9}2TL9EAU7K#2a`Z7#CP(_ zqervq;WT`|bww6s=x|#5gjBo40rrO2@!SB%k-I+vM-=RWqLPDH`;Yb!1-zp9J_ose zd^^zRyRYd&eX8&+8KT#8Z&aloj~%`#)pGzeep&5jwC{=A@+r5~mMK_l^6hU_#Uv)_ z_EY3)Mn)RIuw}dLM-os0kWE60eC1S2mTq-V@!HH6K%;ywRy=gQ1_q(xRqnTN1u0W5$8umBdo0$2dQ0N+{9u5x+{LI3~&07*qoM6N<$ Eg4fEZApigX literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/button_color_a_outline.png b/arcade/resources/assets/input_prompt/xbox/button_color_a_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..9699da0f5f9c258ba96df29944d9edec328a9a15 GIT binary patch literal 1269 zcmVC00004XF*Lt006O% z3;baP0000pP)t-se7FF7wg7y#0Diate7697w*Y;(0DZOqd$$06wg7j#0DZRrd$j<3 zw*Y;%00000eYXI}RPD3?000nlQchEE&#zB^-!G3}KM$WDzwZE9ct};ei49_7NIRV|&kihL3kjjn{Er*m05`x5a0A=`H^2?> zp8{Az(uK7-Nyh#)fQNMPnP}0;$M*q@o1RRYocShzm+a_o2I;>9;KwJ}M*jr>eE2#9 z5WuQ~?xyia_-i9M9yTW`+Y>;1bU&!1(u^8D5Jfit>-PN;Gc`+lQfK=B_}#?HR%Ujz zgYN;Lw+b#z_R~Q&(%#w zNCyOsf5FTBcuk0a0}zWfU3B$!v0rfwV41r?@>;-_(vj4|b z4D|rZ0n{}94?*cbqQVcw+}K26A~}G!d{lef{q_t)9f0J2Rn-P|6Ns~b76Uk$LdFt^ za{+qp(lqB3k^sJBJC*Hi(H;hHT`cfqQVs?1m?vMp%S$eyc|qa9FfWA-xSR+Vxqt6>R3deON(I0Kkhmll>FBt!f9 z&Hw-sT-1--Y6Le-0syZssX~I#j!|Y_=Yc>Q2%i7}w1YB0?!$}(rwNr|W@40H+JH)s z2$f+-Zxy9As89)7i^@=?Rd@mzAr0#7;^#mazLsh%0)&jcrX?u0sI#J3W^5hW=$qGTKFFjZJ)}XWyma7tgY7Wa~I&>-> z`=!%xgfAxS)$HJi&m-hySaCIDFQ*}&zvP{A#pU#U6oq0en2shhG7J^hbCK_7y^iE% zmX@KfNl-8;n)N!uY;#hERVkX~Ys+0nm|R#wJ~lrms>x|B5;UB@OUXyprRf%n>XIqm z&1i%(r4KMui8e0tN}RcyKI0qifFN$oCfj3tJ8?Totj}>BHz4Dsh^OxGVxale%(-3B zRr1ST=R#(&tpPdzDy(XZ;hl*&ns<3%q zu9i9Yf;1f4~rY%2Dkxk ffE(ZjxB-3vC00004XF*Lt006O% z3;baP0000pP)t-s?@lN0O)2k9Deq1x?@lQ1PAKn6Dep`s?@cG~O(^a}BJWNq?@lQ1 zO(^e8C;$Ke?@lRxk5ak-000nlQchEEkFU>P-ybheKM(JJzn=g$|B?X!00Q7iL_t(| z+U=X!aw8!OMR6}|4T}E%YsU{$PF%Jdt?8L6^zPOP2+(p9zAm;f0Vco%m;e)C0!)Da z6rh&Spg#;+DD~F>QiB8E*a0p10~FZt#C9m20I{8V|I5F^*$XiIbepu6WG5CQ7# zK$ac1Y6*~cWZRz204ku!pxOY$6@|7&3xEw3w&?*Hs;p@N0F?$h0JZ-iLGasuS{wv- z{{wp;fQF{lxBv-_EinOBYk-vp(9ql(2f*?K z3{FDudIG4A!drldE*8lE-~#|ufWZR1EtpDft7r)V{|J08+q&Q&yaPCd07LYH1~7Vs z6&YkE0e}Kv@M9nW92$VSGxy*Xpr8g|uz`gD&~yOj&sZY>bSl78!m&K!EASGNR{)d% z{K)$XZ}|d06Zt^8kp6-+1lXhiNCMb~0L>Sm4FMJ@z)AvGh5%g}gqakCeF(720KS_c z;kh+cnf4p8u7fPOIm`z5gkfU5!Oesv6NR|AYL5~rK6L!^aa_q&Rd(Nzr6 z$EcGw5&r$>IQs%*zm%*SrDW2i^pWywlTuk)j;mc-t}RTuwlGnaW=UO|>59|y1t1lt zy7Igkl;@fT#b(iba;G%;03>>| zx_kKVjBdG0w>okL7B_qUH`c_Y?$-YI%9|NwYk>jJZw?{gVFE```0000C00004XF*Lt006O% z3;baP0000pP)t-s?@lN0PATtADep}u?@cN1PAKnADDO@v?@B4}OeXF`BJWKo?@cK0 zO(^e9DF6Tf?@lQbmsrmL000nlQchEE&yP>9U*CTpFAtwTzwZFoijLy|00aX`L_t(| z+U=X!wyPivK%>YYlm7qLo}p@OD@hpIbMIR4T9plCCWYmX2XufA&;dF?2j~DD;6DYh z9<&R`a2mY*7{IJuTrWm+dVW8EcWOn3a{iqFMx(QD4BEU2z)UN1Oy&&${OR)$m;iQs zk~=kh3;r=cPfv#vN81uWJbZo_FD1>7!vmsd24LO!yMQds!kN_BHUQ?dFj~v}iFPn8 z0Ob9HBTKZ*cz;9I1L(VCvyH92YmBZ1I5t#enK*yQf~x~?`OAOa1XlyFpMR7~_K=Y! z2f&)Yz{`DmO%?$LAa-pkboF-EUr`3Iub_1PI{~(XX#l&;AI(xc%>E5q01#@<|9KZf zKEQ5(s^>*gyaM>!w`>W( zIwhlv+wYzMpm8Haq{TCCj5Gt__9RdM51S^AAUx?lA+at<%rNHwoZ`KZ+XIZ(QuqQe;WC(-XeI&RT2JA6r+g;Bk&C9!&R_rj z0-#C&W<{#u2B1g)Vk3(UQR>T$0kA0UBLM!70nm!jCjjJM1E87AM`XaY0x+d&XsF2e zEtb$rMT;TV5*C``p_wUmWzNVmoss+Z-LZIYKq8nSS!{m z%dlMIDb8Bx4kvbEQk9ww4N|k3YvnxAT7(e+I%L?aLI>=?T>Y!_g~ZDk@GnPgof6KTsCtz3%V7WH&)t7_27bt*pm z2qS5k&8hQ{W5ZbegpM8ji7E0bR(SxY*0A5s*#v`l=(uGzpRrpypK8J*_|s_^4`XXf~^{`P{CFWQbY8bY^R}g@Gs#K&H*kZ*Dj8y0`tO zyItl7aAocNv(OC00004XF*Lt006O% z3;baP0000pP)t-s0H5jrpX&gh>j0kY0G{grpXvad?*O0c0H5jrpX&gg>;Rqb0GaLp zpXvag>i_@%0H5o_(Cd8w000nlQchEEua8eZ-yeU^zh5s8pYH(6&&;3z00U@AL_t(| z+U=X`lAItAg%uQ(+w%TTn{2Yl>L~VUXeN~^`tR1rr}X8}^iCiD*row600zJS7ytuc z0Q{!_VVP{)|5~)u65a;jZA|&bQnbE5z!KN|U~61@0vP+a`WJ&WodC?7L7Pnn0DO5K z0tgUP$}CGNNCN0Jvu)L403qfoi$Mm!Y`M}lBLU!YgSmD9o14tG0>s>Cu??VkeUf16 zxBr+QOx@Qf2%Z3R7BXE2;FA0n1KOeAApDg7%H!Z+ZfpbmAwrJU`d^Kx6rSI|EwP^ z2=Me8TPPH&uYwbv2oeUMk!^Ivq91v6dE&X?;fLox9G=5-!-=;8CC=lMd3$gLSWtC! zIIYg8j#~f#i)x#1f7=48qbvaMEJTvIR*YkoH;E}st^ib7y}a%HSD~G#06;2{{WW4W zN5^>*z_C)gJNqSFL>vhKNRi`jl=>*<2FMF907xsy)gPp05&%)-_XiE=AhVMIP&X*u z6jlStxRnF|HSR^=1yDx|0bD;NdXSO=cr9$OiW37k1tSuG;sCKu#)<))-7swL23TLh zAgu2NxTYespCXb8@0886l44{+2*6eM)Mx<2pVfUaQZ>i3nsR&+1Uxwalw-{bK%ZEG z?B-~~mt^aUC_E96!RVVs>Gh3qQ4WbxDh!ruCu_rl5s~E<4!~HDsf`Dd1r0!Z|6J92 zG(!)kuJmDA52xVqd?~(K^YL6GF7WE^-@)OU8x)Uwph^M%Cq1b47R_@;jir?LI~~N# z+kuKU-RDAc;m||mVzf>7!Mdr(ZH7Cgz7K%Mo7H=d^>;?Myi2#*a4syA{Q8P@V-i;B z_1D0=Gt$xkntN@h-m(D?0J58~2L8>dm%O_5HJ8`AzXK50_hOfawswF)=<+Jok8s59 z>r+E4w(s``#(0{?NT^XJa*)aOT^||cYRE{dz$h1|0Y&#yE9VJg5BFycG%hs*jo5rG zJkrT7f1)FupS6iM)C!L{Gsb!sSgP)f>1Nho^8$GzZ@8K{+8rh$Cbx+>W zN{Y^~A6GZt@3q}e9IEc|F9SU17H9QfB`T72EY%5x6UA`z%u6m0000C00004XF*Lt006O% z3;baP0000pP)t-s0H5mspXvaf>;RwZ0H5mspXvaf>;Rna0H5jrp6dXg>j0ha0H5jr zneG6e>i_@%0H5n^{DqYO000nlQchE^Z%>bZ-(MfkKff=Z5AOiZrfC-d00fjtL_t(| z+U=X|mZKmHh5=C&fqMT}yX{n3Q1XE=yJydlpSu+vAa6(@BOd>_i5uVsxB+f}8{h`G z0sd0}zew%ur#USb|JMLk?ZQ@Q!Rht)0TwrINE_Yqn*c_mqwfsb{1Si>8?y=X3jqA# z>kya#emL0OD4vG@X;O>B=7hob1W;M`^H8+3WtcNW)eXSAv|ou#&C8V3`91(9nHa6I zC83>74}j_0;lMI2Gkm|8+5z;}vf0koe-)!!0X_>lR=K$4V+*bgz|~LxxCpKVAnt#d z9@T9MEISaa@h_Zm&r_2{fB~qeri(|tkNQ>D0OAa;j{haVRuBg8`}n~;E;keV@D~7V z%l;o%(bfY*2h=qFlYnwag7{E98k-^}0s-viGqlIu`^hlZ0ciZIsy3sWATR~A2;d|I zKnVgn0;WfoHerWC5x`K|scgHV4I999QQ%2ZjtgMWbM)z7%g*WPbRe(o;3W{S1j-h; zj==Ho8OJ zAthQs2Lkw|gyN(m`HSA&C*zM$>?47B31FL7GS>afw!A8U4rqWCv82>S-s#Fu7s)>$ z=@T$9Q8ri0%T`|&=Qdt0YRkV6Wg8qAG0xjoY#;bO-}dDECFCa z(?Cpzm!2!+JaUk^f5qXDlZbagfnL!_Nbu1;$o4Cm_-hYuX36Ef-NA(2yt=w;It_bb z1>B26zF5~?)5UzZ%S~}IBf-8YU3qVg?1tUE3e4Xwea2rfFDnehDJ=Ykjqe4iXG91!X#OlOe_<0ybwsu3ZZO zbMk&|GFN->631f#+t}Gi2bTyetDi1k6j8#vV zq#Z$^wxC2{Uzt<@B&A_Lm9qeY$|`P|#b@l2&clt=AeUq}%p!-wy;a|m&X-@SD{5u? z&Wf_$#d3$Y713-}Ve{fR!`Bs8SC)2rIpd?xveIYQ7q^?KT-*MtZlCG{xNGJ8tI{^J u2y3;v7B$>|VQ~Z805`x5a0A=`H^3i-U$+`K6pE1m0000C00004XF*Lt006O% z3;baP0000pP)t-s|Fr`Bv;_XO1OK)J{utdLJaxA(Ha7qUzyKHk17H9Q zfd3RAFOEw5YlYmBKMf#JIqDNT3MG4hrL6VB*0NXv1iJM8&OpHkAj}oCIT!%o?qvud zK%R~&vSZ4c0Di4vTfG`UF15)@)&U4xZL}?D0PMBH_I?1WT}HhCrFL5B1L*e85;(i_ z$L@hM_s%Z~yRa zWwwk+ECAmNZ1=yti^`at1t3{cnI>5hBNkw8&g@VUoml{=8sD26xyS-w)1bYzkz84T z)+!sVT#RlNoleHNcgZiTKJEGX}QU? zI!G24lfM5+v@yx6w*NKDc1B(sz)RP5)2AH31%U1$tXaM}O`5CQ)Uv$x_7{Lz->WST zsa3$Bw7e?q5{_c-pA)f=?e`DJc)DdIoRoC00004XF*Lt006O% z3;baP0000pP)t-s|Fr`DwFCXM1pc)H{Gd;8e{000nlQchEEPmizPe_ziZKff=Z5AOip#U>O000covL_t(| z+U=X|lB*yLhJ%6v0($>fyX{shRtVvvXXnho&)rrYAbCk*3hN&a=l~s{19X56&;dHY ze+pm~2&3P_2vGK)0W89}EijxB%l844;TtlzQQriR0-b$l5b{d^a@>exlwSa#PoIZC z1hB)0-SP2Z`0oK?{5Tvp*p>k7^7wfOTACW>3nFUti2ws&hc^{o_4e>zRt9japmhF|0NcShfZgYJaxEV6c!n(i&^71( zxr;g<;1Hmy`5y!+L4w2=*;+YRVj(er-F$}nxO=}D<~#s_e=BRu=pg{+0W}VAbA^Z} z0CojTYm+fymqHLgrhF%J>>e%20H%ot-dxI10VH~lKJ+y#P7l$EskVu40mNM(Y=H}p zTo1p(&%?VWW^{xz4B< z2?D_DlaPw@JaxDJ{IS>vBI|<4F)yh|?VK&d6!n8Nz=BxPZNJCoAI8wBtki`;*jzOs z%Gxc?x637YBTY#Fk&c9Xp99#4x$qFS!W+Pd+TfjlI{$8kV-i3|{52{E8!-!2;j_H~ za0)<(AeD!|0C*B0$nVndL6Tj#5fB;hf|YC zNaorDIATb)W@}rPZdQlo#Q^lMT!cfXT7Z$9n4qayF+fIYh9;-Q02#^YE=9U6fX+zI z8Tme<1IU~L1zMsh1js7U9G|xB0Bn3skXO?x1Q2;;x+C9hQYrRUG?FW&%SN>V+Q2HW zfE9ODoB0)1dIoIIxNR}N<=BY=7Cp2%1cSbO0i3$Q8-oeo&DgceTD-LLGq~8>FTg{% z>bEw>tIKGR2`ST=s|@kT;^XEF$-S!~AiO+ALa(C$%!}<{;px|(kXGf_ut-k_E1Qj>J)ZZhe09#S-nlmmK|VK<_-z(z-}9@7E5*0A5s*$4x>bleh~&&VyE z*V3>aKzXMj6%6|NiMB1BPhHDvY9(uDMq97a)ai9bG@I4ftPExPyyoi6)@}hu?|)xjpZ|UR{`m~wDz;cNFfb)~x;TbZ+ zkC=n28I6L%6R$BInQN8ZFV)b^_DYJKfmgJLX#yMfafZWh%~{XA`}_G(1a!`8e<#b>j?~POjg|AGU4#g}cZcUqK zG3}UqDA++|a#s$+Erto)JAN{%+~)n~|KNvnf((O?!$u|%xd(Y-a_lymcmHItIKFwXhc%hb4@pQi5}`Nr>9s}s{0()jrS(+4O!b%vRUIs&8DiwIx)IE zW$D)?Chrz*yYuwnw~B{ngO)xJJ=*`e`@1^-*XaHgqBXCouKa30ij+_opV(czbVmBL R8Zad?c)I$ztaD0e0stH4WxW6Z literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/button_menu_outline.png b/arcade/resources/assets/input_prompt/xbox/button_menu_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..1848da3400b7f7ba2ee9c5ceb90721bd37a205b9 GIT binary patch literal 1086 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD#8t!5Ke3p%O0i#I++XV(G2j(CC zjB{9@o;|&X>GeOU150Wml*IPTTF1U2POrl=e?7|uF4@}DfL}@sclBLfe`RI(^6<1k z2SeVE57syBW&Wow_|<=wvw1bA0^j=gZ7Y`FTxhM(mBn!G{eA8eS7hFJ?p@8ipj`O0 z`^>u+%XYCH_)+)Jp-9(uPrczM#w)ispFDYOud-VqV;r|uchH>LpH0she|&mbuh;aP zA?LUJv7I-LIecPP=&gS=rFzaR6($~soQox&R+LBo=_+Qp&2z5rzuJK?1KtPMJ~q$u zpMCG%Lq7(IbN}ksEz)GaAtaFfhuu+6(eYV(QrfH&J}RsmcAY=4e)sjs3$>X8eys0W z<802jf#U(ofp85KCB_ZP33FE8=#b?KWSHAM*C|>#YBFQZRE|Gfn*OX8K0fz8_31~C ziJ-$#`E-UCB??CvoN5kAFuWFGk!tX$U|>)1dKT)j^FcY^BZf6MPfTWTQexl0aO{d) z4fBor;X8IQy!-xs8pDDh&koiNYRaw*KWaBsedZGS+;qfc`7MUvq|An23b%!Xn|JJ< z^@~AGE=J({!G(4Q1il~AZ}=xAe`n8z^Gq{b_G~(P?E5!vhd6G9%?vLU9;hz5GF#e# zIfH?dAy(c&m@)5s%Xa1;+5F1?-^(9=bgRilz0rF z&imHv`E0%Y##{cnHvTo790rP<0!lNq6C5^ko>6HMj`w2D{4=M+^`onfq$2-?PZE(* z+blOM@tV0wUP0Oa#FVp7Sv03FIlaj4Tkb^BB{9~9XD&-_o*DUmO8XQUw_j{`IG;H6 z|7v9K0bf{`}qu$E=@ejz`*#<)5S5Q;?~<+ zw>LEz@URAmyx6<(|NpJWPcKfGtY+J1Y`vZ-GAP>XQPJ-r9iUNY;J|r?q#LJ{qtxfI z=PJ%(N?NAIcj_XCK(RCr-$L#ivAKr>@2+D_as8#PxSC<3N$C^|K8~%DF1(8yuJkDN zGHy!~yUn3se)vR@L)Nm1OBf#idEO-4u>M-ZE7gX(HveNca5H$%7T}hjaBGT3OhXDo z$CTy`FTxad-SyZmr@Knpq5rXGDK{U3-?Vt80v!j@XW|@M>;gxAvNj!Hm|*k&fv0+d zRf(pQY_oZT{2wi!#`jF09;{8_DiS|1yT!fx)eV;#<|#WHF0sb2`ilp?3Xj>{U?zS+ zq~ZJd{&Npwfx&LaaBXKp=thB$%VM6dSQOFa%i#CXz=$#B=;IqK4@9MfxE-$MoYY}h za&w9o*M?cXFJDSXFhtz`blK2=L4aG}Iiu41r&FFBHM{rQ*6@B`axjNF$4Ib^5 zyIBtl@21S1STA~|;}1{6^eHc!YX2!{hgiM-;}^9`_4XeX{jkgXpZpJ5XTC;k+5Mb| zn*FK8@%%co!s4!Pn|)n7(M)e|me~aJjJt7doF#_Yar}S8x*|XS3O&dda-(>W7?<_u gLqFI-2?q^)U{Bb1PQjlkn-e7A>FVdQ&MBb@02xXhaR2}S literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/button_share_outline.png b/arcade/resources/assets/input_prompt/xbox/button_share_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..9f9141784512d27c516c97e0583215ccaaa11610 GIT binary patch literal 880 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD4q>EaktaqI1@ zo0A?Z@VI_#y1DWH|Lo<~X+r5uXHr+rdZ+c$^oG^62@XL={>^taU|`0A4y;<;zF}&@|E8HdP5Nm(` zWbWTdlRWodXL+gT?{<|-AWmO!nc2RrHMX+17^a20%$z2Y9Jg`z76zN<^E0pMq|b}a zSk2Dy!ffAE0q-~g)xX*CtIUHN8yE~aYpzu-n15B*)+_Cf&5>(C>*1+)uCP6u$cG>f(C%*7M}s%~uPJ9IvcpnU@im zvBhyBPurE_6MoFjOWc=lB%jgPb)xLMZ6!l7kIIkeBQs{1#h!9Ip5uOVzLx0gTe~DWM4f D<^PWA literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/button_start.png b/arcade/resources/assets/input_prompt/xbox/button_start.png new file mode 100755 index 0000000000000000000000000000000000000000..907a954a2a1b8f7635694a65b4b5bc433e1437c2 GIT binary patch literal 879 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD4!>EaktaqI1@ z>(g2kc-q#AXwLp#e`~9blCqcJoXyWw%##m3lvX_HKjYc+)7KOjn6RJ&`xz&ch;%z1 zxaOuj)s<02eL_ylX(xtB&dZJ}urOX(qImnul6g!Y*zd0A_#@aL^5I6y8TJKITr-yK zWMCHAq{yGpl4SObp+F!)4YR}(bWCufFEhTAG8_hgw0CPxZ3+`Sqi&YLhPQjj6zX}ebm04xQ?F>)N^C~Zcw}{H8CwGhFd5Tqf z!zOH4uC_gOl0u>C%#*Lpt_cjCz&!tc+KqFC)k|FJ@x2nk>ISA)2@6! z;IMJlo=g8@ckWBrTJU4OwAlUon`SgTe~DWM4f DqUDO< literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/button_start_icon.png b/arcade/resources/assets/input_prompt/xbox/button_start_icon.png new file mode 100755 index 0000000000000000000000000000000000000000..ac6c97fa6ec6f63e8d5a991d889f890343f92acb GIT binary patch literal 666 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDpom-{{GUA7=s@h6eWVDm-0V%j)}R zN5<)sA`?z26!uK&VOTlA$f89vfq(9mi-+sP0{oWPb37GMFtw7^eaX0Uvg4OEtRY5S ztC(|A#AdSvoG|E;2{>u8N+e;<|E&*L70R+5`h^bU&HJx-gSElh^2l8N6;Fhwa(}2` z(3d~-D7x|4*Nltz8p=X`)Yw;Z&tqVEeY`GgYQ}nz2!?yoAF6k{T(E5LF>3IbAlabE z>cP^?_#&yn;llF+#f#nU07(cM4a&c%eL<=lySjJqyn#&b2g<)C3OePQR1TGoI zJ+Uq73URz1;mq5*8LvK5joHc&_wVXjfzZ`XOLUH2T_f_^&h6^EWaLa=jPgpe0q;pUNu;B@Wr=fYCW@Weu(<0XR`U>*K&62 p{$p{%dwcg7*Gv3gfEHmOt{vk{?sG-@%sxLrJWp3Ymvv4FO#uCLB`4x;TbZ+DxmERt*P6u7Cz6kp&EJ z=7;+Xd*(j!f8%sVZq}OjTpzq|tyb3ECwxG^I$425P{8NjY~|Snq8nl|{<%fyG1lDC zcmML1;rFlS?wiltS1f&b-gtGV{{A!DL^sIkD~9jhx7V_6?>5F|$*w!6i9GJxYQ2?V zUbp_PYdX*81!t^eU-QDGPC9#%&$CzF>uqXa+@U1@r_9hvvh7V4!@?cKH@4+9 z1u!o7{>tlMz%(Pi4Ys%Ke-*bfK4sr_ec|FWD=s@K*fGznJ3g=Vyt{fs!FN8VbNkN< zGgWJDignNl_h4Y2@I1bZZCh~XY(~!t)h(B03h#>>F===oSjgb>U}vRok7=F(gRsEm z^+$RRL@_q7Op|0z`lWS+L4nbDQ^`KwV{5q@I3E2x_&~m4VxPfs1_y(=`Yu-Ox1t(! zHaw5@e$JV;j;*8V=eH{wOz-k#kAlKB zy~8ZJhaa973Vb!SLn^rN)wHhLX39HjD~yYDZyR3tH|^lt9X1iA3XZqh`Bk2JOw(fU z`28mPrt`{n#(>{XlbJ22OfxjlV946PhVezjzaK`zyAsx9?Jsd?Vvwu){FlwRS$@JF zdDk~JFTWT5iMh@B$LibD>|5Wn{ujDFy!gA3)hgk^#Cti{zo_Ll3G?igmB`XtS>%53 z@v-w)V=kZVPmX(W_5YnY)AY2~b2;rSNEVj%{7|@e%fBNvu1VM5E~~VZdn#9FG|{GV z-}}|Kk0nYr_N%Q)nE1N-;C_+aQru$uz0DR!KI{(OK5L`<$!A+16)ep7de1jPEW1BD zb@$uwr^^+eWC;iCnme15Q)7F>(bsJ|1$K+Be{Xqo8Ao_wv5%Oq>@Rr{^n?gXlMlGx Y$n5?2#XhG7nD7}qUHx3vIVCg!06}S{n*aa+ literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/button_start_outline.png b/arcade/resources/assets/input_prompt/xbox/button_start_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..ae48df9cd77928037e4095776aa4cca2da6b1003 GIT binary patch literal 1077 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD)x!e}4Xc{`U-LRIK(hFfdQ_ba4!+xb=3{ z%}t9H1RM&J+5i4uFUxys(E?o~Q`6A9`sQjXaxbU8^L|#Z!@?-QfCc@NX0YR{n*7wU zuDflbIqL&2z4fkI_c$6>FAY-Q=x1oVwKPh{G@kKF`|bY%SJWBSO+UY=()NIa{k~;c zQBO0yx0~8ty=eY6g8M?dyyBa+^~F;A?p|l)Ik-5&w;yTX2Rr!;^WxR2{eP zHj-J8U-^Hk!<`w7I|C2gSoPwDuGVUnpgxB!u{Rf=`q&|2z92iY;^iZ;6ozjKc3W8< z9Q8fRu!DC(Znpn)aUGU~k_~Hg8T4C?_npIVpv)|jC<-Z)bv+V#xlh)3Fw zZ<@XCdzW!eyFTcG&dJ?t_KB!QNAZciDifV6n!>YTXZ0?lPcC0M941fLXxr(hlm6S} zg_qyt66@FZ=4Vtq+h9~B_KRbIaQ5DJQ9o|R1|Qp7yk~x0-ABE@914>(w(Nao&hYSk z-O+A_;3UCRhUGgY)NSwDFiZ(8_?4<~V8fAne_1A6JK)Q(XLTeWn?;bHa6>>ynHqb6 zM~Uij8GDvk@P7(Q3b&umb!!jB61t&FpkKgSr7gt_* zmMJ2^%8y&9@MGS?sfBxl+}A3k&7XB?>Uyq8wr5XoaTop6xXHcEK5pm!<5zE8dwE`4 z-=163%U-^Fb33m`;I7vV+gK$Yr2Y)d`nZ1sqY%r1>EF~RFZ>a(q@pXc+~V6l-Nxm~ zby|$GwrW^xaWvG)J%9YuvgjG_XP5Y%b+~KGzw>gWbkpL;$M0=k9e_vjoKmL9F{`m~)GBe#77?@Uhx;TbZ+0&N9@YdSPR6FhKmYu1uiJ8~dx~sd@7+A@Kf9&uR8`apq~^_!Nn{jDfTDdG3uGFr zeuvII6lljYVP*KfmA5p1Gxybf>bCm*kkjK!(U;nFxXPK=0!{#&l^xKQD?El?=w)uhG zKZg%o0ykGI;+wEqbU_;Lf*HO4^)$E~k`HGUF>K77C^F&Zj!gm|Hn7>GFeH6e;ymz) zQ^JPx;A++dz7NGFi1@M^s7qWB%@q|W5htheR-~V%8!!z!T|Mm-* zYfG3eTz&h){~$}koxY1_9aR-pO;2%aSQ9rzit)?oB58(AI_mxmpAw5Im?rpdPXEg6 zAiw;9*i5d5+IjBw4BH$V?=tf;+*x$+U1OT(we)Q-br@cl-7Wsk_~wIO<~PQan$1!b zvi$z7`fqMLlWREB zv033-^?{R%)I7|)o6fSENT)u`O5C!$<)HCTjzbf6+uWJA?qB~>ueaes|L5r4o3Ffe pf0|3S$mUneuhmEB9AJVWY38Df+qv}TMFCSZgQu&X%Q~loCII76e~AD9 literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/button_view_outline.png b/arcade/resources/assets/input_prompt/xbox/button_view_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..33e28fbe638eec9f97e3d50959b7c3a67cdfd14f GIT binary patch literal 1168 zcmb7E`#0Nn82)^H;}$~5=(LKWGWSc12qn>!q;bESTg;ugE4Buy9kiQ!doVgtp{zBj zyVfPlp{}P5+ZfxzsrH1r#8@yI%g)(9u=kwzdEV!o=lSt@?|Ha6qmbv3000z)O!5*l zsCIie2W; z3(M0qb>JxOGC^y?-y8toEeeU~lfArM_r8{;C11#D)WY6A^B=h4o%lmRIr_!^mc1ao zYFcg`L6=)GwdRA14R8iS;WdOGaX_vb^Z6Ra@y|qTs108j)L78E_zGUa@ye~7Pbh%J zG^Z-+be?g7}N$lCYumw89*ConGB!&Y=31dZA; zcrO*BpC-LKDrAF{$`73O#y7A!M7}CIN7UCMt`)^&tbA+83N&jbB{XaI+wfH=_SC=4 zs-X_lNR!o9=Tt1}K>#wKS1)n2(ouP&i5@dL;~}4U7<*mnuIyOI%>sef!8lpc?0eB$ zufc~uqm&3OFTMdTe<*o@OFcAgMvklmig5XQwLEQZ!grDz8iBSVtI^7wDiG*OCu2lF z+HHXXZI`*92%Qd++K%Tg^u;Ju9Gfr)O^G_>P~Q-y*j!g!$nuRE^0+ZRS$5VS?( z_Xd!32jR@;2{YBqV^#K}IrF5ML(96HYoqCOhX`BbQuy$ZfEurqL!ScbKcPb9Rq2-L zasEBo-6!UJ1}7KFWohU3*wmYJvBLrx5tvQQq`YB^2eWElCtxpW*=guBFYc}5{8vWt z)l7$uWp`d=)e#y^3zU@6Fj6sV;3BM)iQ||Z(s_>VXWTR;Dsd9-#yM3LUX-%)Zgv@m zC$%vaDiJosY0HHu&+43kIaE_H)_k_lTveybMqg3iytoN#*DNw?>}{ARH;`=t*tn&# zG^??O(2x2^duoC6$Di)@%WlP47Z*$o31ba6PD>N)JPmC!Eh2}0@hr-c-_WEkeXbLg zUEg1;zb!OdWhssn|4L6C2%KLQCf_>w(R6il{lnAwopJ4l3s2hP`DbH||FxAM%?Ej4 Xa(mF)hm?yG{{%p};YMnAq~-quO_>)B literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/button_x.png b/arcade/resources/assets/input_prompt/xbox/button_x.png new file mode 100755 index 0000000000000000000000000000000000000000..b04ef414e98dbc15274a7525c3242778643c1e39 GIT binary patch literal 1037 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD0p`xmLntP|TKVCd=^Q zX(fk(LxARHos|ll3$CBp7TV3&GGnz4nv-v5_eXHXhHU!j8OLga}O*#`e0hLhJBST=sG7i!^Zu;spWj(tPq6&>*d>4~3x z8hoV^9y8w1$(zQkAj#;W;&mR7>yO zb=oiVZ*$!ig#(wfa~1}y*vI8pq2wXa;I{3eXm{O{t4}+hH?_;p`(IGOcb-@M9^Vv; ze*b504~ix@UcLHoi`8PQJVpkcRk{2A=5nv-+r)5Svw-KbD8s(Udn(Heb}|K|e*LuV zdwgrzA|{5FF5j+qT$Pc1?#Xc6>*c#Q(@O*kRTvap`}POET0BEqL4OZ}!div!`U$f< z8C-hr%bP4{KI6yaaCbpTa0IW)<1itHD?7v+#T{gISqlD6dEw~A^vma}=oKJf>$x-Qgpd8q9PHaqXtHIY-1A^dLeVn%`bu@TprS?qQIZekuFa42q_IPpcjsvTA*R!{0 z=LOijYt&->GyltiThS)xB~7OV-FsgmV4Tmcb*p4P_t{ow{)yce&F-?lUM9C)^4*kQ zb_{a)Vef6P-Iu)kN$+mf;hh@-KE2ky*6SIu^m;t=`*R-N%o*>t&aFDlX2WnI?~v3F z^W|}$UR}E$6ZiTbpF_<3?pV27w;0(Lo?abce@JlmdVk)HH}>6^mw22hr@YW@<_Sl& z>*cj6Q&uY~uVq>sGlxy&uK(IO>OHo1=9^yFafQbu>GReP9_qXQboQ0hT~oUkpl$Q$ zjKu5Mgq6FDm(7lBKFj^2chAGDXBp))I%eEadp5zke#hM%5fijDtq#}4+^yJuz0AJy r$iC>etL04p9Zmw}5(5Tsma%8h6FaAHeZ&5<3_#%N>gTe~DWM4f%?;yp literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/button_x_outline.png b/arcade/resources/assets/input_prompt/xbox/button_x_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..e5dcc05019c60eea0bf3da2aa1ce16deec8a6a5a GIT binary patch literal 1336 zcmb7^|3A|S9LGPKO=HGzOevNxbs?4=^QCXoFn7Mpx6MW7YeGlK6@B5uxsBtDbc~hM z)S;q$$?|m)VVOwQ$wZ@W9rCSwne2A`2lskB-tXu8{dm0JkJoRnEQ+TqN_Cqm001aA zGRa3l@V`PRDf;q6{+a@?D53`u03NWA8=-K;j;FZ$Im`c#6i z2XNtXKdOyxXn~sgbt+3pU}N$hG#f~7*E}>p>FY_p^Il8&A zziDt%;Af&+M$q!P8b*@wQz#-+;}w|cm?b7W!NAn{i*wx)VoY19SwFLHG+F-sG4PzN zz35iB+|X##8?@8(=;1BU)ms^F%T3m@$HluPEXs^=TCz$Ahh{zU<=Mj)Gf@2msPmwF z*kxSW=Y{D#gq3RJ_IcaV1Oh5u_q}bfWsIO59jU)vZ=dirbxL`iF+?kYp({|OU1U{U zqZ1Jt5M!lr+!cwd+wlW5M-6n(DF^UiKCFy%O4pc>wrAy{CRW=xs=rAoB5bOw<{HF$ zU#%8<9PpinE-Ux+=M@}Tf#bGp#a*s21FD+q%#A*Jt7-FVNs$A&J+&FJGJv zhJ;8K^pab0F?>m+V}R@Wn)Zb@Q6R@O<);7LJklxa``u9GVGe?-u4lP_QvL2irID^U z_=(dui-=Og@FIq@lEI@QLd4D&!_*$qdVbi2NpIRo1P1_=h{1O}dl z{~`+deO`aETklM==T!)_d?jqnrCQyvbj{a8*EU5kvF!Tt@0`Oy4uvzKTP!6Q9=uEC zP;i*nr?n~h)GUU&wMo%?r5yCOp7grL$Pt~pMK^=t&DO);>KInVy6t6HS{C(^`Ng;M zLKeF}AAi?q{QbN@4C9YgDf@U8WTP0|vlvfg{i_#x!qyNWs(XrY#VRRJ#-Pw`Dl9j) zFq%m+gqA9EHawF)u!r-U7V84fhnfy6)Wi}Rl9&Sm8V(%2`sa)6O(XHW%p49mbHAUt zxW1u2)?SFC;A?u$PuE3N{uV#G9von>DeC!sdP#gr<;|1p9=vS+Zf{}hUdB834NF|% zy!krT&Gn38TXtEMt~QQ3uFJr%(DUu%eYujpi&+?4cI^5!+3;N8yiOKD27xD2?%$hd zs5t!vD?@bWoc;O#Kjfuu>+#TGxX+#QegEF>Q#0nWDntg&RErc+7E8!G_a;DSZY0AB zsr$2~cb;HMxV&KR)`>BZ4cD}!*FN6LAa~n#$HXg46&gh!jiPuT=qaS`>A%Qq!LH&c zzm(&HAfHO;7Bz>rKhJz;?bycf@i((bbi<;Febo#XsVJ8B3qC%oOco%sNp$o9ir3^ErVq${v$XeICBV<_<{ z7tVcEIQyYz<|amu9~-Qqe)KnG?mW50%0orT@tfM^3G9}_1^vwI_vg(T-dRNL&~sLSLdb3a{Exv=h1nS zI-?RhH#=P0ePC&p$covnhn}(S>NP6d8gOf_>}S6`KaHa4-M0_N#ZA$2zuEkwF!$5H tCz|)}c1WCDzRbP%{|0D6W;6h%YJOI=i&-ms-_|k!fv2mV%Q~loCIC5QwLbs= literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/button_y_outline.png b/arcade/resources/assets/input_prompt/xbox/button_y_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..affbcfa3c02005b8a81b942da35ce8c53511b828 GIT binary patch literal 1253 zcmb7EYf#d86#el~NioO`dT5BY>8g=YQtFn0De2@ZGb*!EoU+oCDcyo>`im*Bw)vna zQy0cI&2$zjMSK+GBS{T>lp(2TCFW}?Q%iGn*>C%G@65U9o|${*&i!%(VIh00E!SEC z0IUN8{K8EP{R4P{FnYunM|g2TorfLG+~p& zL-r?yW=joMj%)3ADc!}GPhgw9^F?-=q@Wq_t}@|~PTw8tM^atd z%@#G4Rg-Re1EF$38@!nUTIT9bza$9P_rh|F?uubTwUYoKqIuhK$7W_fA7_}eDkRB^ zLh$yPC)7W3;T(xDbup6!Iw&JcHCf*FgILLfIeVlFjoY-UH`416(}4{2r)JJ8E-BX- zcYw4jZt?9O9e@!=S}vf|aLmdW>nX<%l7XET#q><%Al6#vxT1PhT@oMfX$f(5K7U6WOWBn##$;R`nF;j= z*!w8<;jYt3S+{C5_g622tHy;nzkl74QoXqFP-o#d&Q+7$%K|0~Nlhzf(mhn%LtST; zEhF~NJIX3vWzcj{6o*zlNmJdnfxSLB5aeFJnkq)<_%)xg@&;QKUq4s`c`qeq#M)5z zEKpo6Y{^y!h8{nGu`H>4;!U0sT(lt$BQMa3pG$|FZ^3mKxG%gHKj;{6YU&#a$|C04 zsC&RuVs675BPg}&nBvU>x3%0?F!e0RLs7#UY`_DXMK8!9if;M@Kan@CKtr5$kFPa( zT2XoXg#iIZJ;IY2AU_#@M3l(j4L5^nS=80trIAo>;cZFTVKS9|Cex|38LW9Ry2-zm z1Ze`aYvz245$Zy?S!A&bQtjGlCzelwgw)`igWax3Ec-<5dc)apmsy5Kp4+`PdKFx+ za+N~33kM43a14%lf>v>stQ^(};P{=DUdZ&Psj$M@XSV6f3@>$6ZIShrU$-iBZ{1b9 z6=T)gTFTo_HLHCv31?^dr(LU?~xfay(mOoa-U6sR!u5; z(w zk)dx>DJ$v6NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh0azWsXn`ThGD4zHCC0g7Jsba4#HxcBy^Azzb$L|bB7 z--9=*DJ5Unidga()D_ko6ENq9^7Vh2wqU<%%|(j`zu(qg+nzc3bh|fDa~S`e?OW^D z-jh#Fc&z?9bivnyUVz0NsoW_CeJ-mWV8Ps)7t4_ zQVN$F4l=Z|Suih=x*&O=8>}qC{6fZadA1cM5AqsfnDg$R-#sn;`?IzpD;@j#yM_KN zAfqNUsLguZ>L7adqmx4avmfF9j}zC-{jNS%G}T0G|-o z=Z0?2joe-s02!{&jNG0XIz2Z6F@Rj45}?#P`LIwRi>oBaFZh0azWsXn`ThGD4zHCC z0g7Jsba4#HxcBy^Azzb$L|bB7--9=*DJ5Unidga()D_ko6ENq9^7Vh2wqU<%%|(j` zzu(qg+nzc3bh|fDa~S`e?OW^D-jh#Fc&z?9bivnyUVz0NsoW_CeJ-mWV8Ps)7t4_QVN$F4l=Z|Suih=x*&O=8>}qC{6fZadA1cM5Aqsf znDg$R-#sn;`?IzpD;@j#yM_KNAfqNUsLguZ>L7adqmx4avmfF9j}zC-{jrxWP!PsM&_GSk!QwwJ2PXqL0wqCy!Sd_v_s`G2U*B)PUVeW6 zeuna`_uYVUKRsO>Ln>~)y>*kf*+9TGaC7ow)pz@Eng~cJe`4u;mz&V{$6R@LQ86D- zEeL$5Zph`byw3krW|=R4#H++^sR^qBFEQ1aehzZj!WhUkBlmy~>y literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_down_outline.png b/arcade/resources/assets/input_prompt/xbox/dpad_down_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..f31d0a5c8a0ffcde2b4c4e5888fb654d15bc5c5c GIT binary patch literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1F{QUL#{rl}1=9NxXWME)q@pN$v$+-9Sh9TD>1Ce7N z&)7DZZ*YI{?$MhC?i<84O3E(;29&IJ+wx>e*{006Pc{63OKR>1@64Q>ShS9&z%Hu-GY*X*Sr$MnbrzvI33Vx)M1fQy$~DuZMkpx73MR7Kn)BG ze{2m-Y+T&Xef-vvqBD$jYkZB4>s&Z~OQrY>vo0f-Yy#(mmkn(Uhh;v0PR_me|I(lR zEMRRloDrVy<&zxVhExkIxWZV@^u+3c)(LCtS>HY8&AfSn`>DzF`}=cM`*oIm+Y~MZ z)*`_lpkA!Qaw?^-)nVS5kL&Gv+c$2z)&JRX&0ZOA#+N`f22u|=*&h}u2^S|AfwXwK L`njxgN@xNAC~~Ys literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_horizontal.png b/arcade/resources/assets/input_prompt/xbox/dpad_horizontal.png new file mode 100755 index 0000000000000000000000000000000000000000..09dba4e577b31c522b8e8e0bb4bd545bb24b77df GIT binary patch literal 435 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O`1!2l#}z z{s)8SMsCjy-GIy&1|X5=hG5dj^_dY!5D1<7wtE1_nk3PZ!6Kid%1QdGa+I2(Sh|lT6<5zW$m= zBy(dstWYpOk=uH|ME`2`OF3-neUblVka_1H1i3BNnc=7 zRoJ4o;5t*4a6lI0QcjL22PEAL2R<|UaTlm1TxALqHppiD{Q3Il&wqa?E%3L#xTTrR zU`xzNWYq<-58nQaj$h- Wd<%a5J@D}#NXXOG&t;ucLK6T=O|5zW literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_horizontal_outline.png b/arcade/resources/assets/input_prompt/xbox/dpad_horizontal_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..5f7094ace2626fc293f06f49b32edd4fd2912008 GIT binary patch literal 377 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1_eEIzK_Wk=A_S;*k14Z9@x;Tbp+AP{xp#v^f#g2`WD>gGXuI0a?FyU7NYvU1iErE=_Gk3S;g~@cRVc`%^aCq>% z(XUUHWr_VYx6_3@8OLv}ufyXJPbP-X?Q!chik22QpRMg<84uJOph}+#+?1=pb%XAXYKMr q`)gu#ci$h|H8uI)MxezGGu6F_eYU2lEp!9!F7srr_TW@bg3pFbUum%c0mKJFJyWW&rK-PK2r6s$< z3+moq33+`q6sQ&i8tyZnnf%#K?bAKq&2|g=+rQpwQ03Cy#UOmNN9({ThSgjzQXH0Z zb*M8zl!M6w@(j1qjd>I!&*bG>t22b&(60-xXER{4bx5+#XDGeElC%9Y+lNc65rQ8s zu$Cw(ykJs7(#`OWU!sIDj-_W;gDcCHT@63~eg0D+KmFdZm%_exUogyh=*5Ply5T*e zUGjf1RvpXF>s1oY-PrHSIDLOv_x`J8SJ~DatKCuW&CJCPRlh^#1KWzErMbRm+rvRZ Mp00i_>zopr04OA?5&!@I literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_left_outline.png b/arcade/resources/assets/input_prompt/xbox/dpad_left_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..c813a64ae1857efb32621658edd14fe7d44c0323 GIT binary patch literal 389 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1_eEIeH_Wk=A+z*}i0E+(fba4#HxcBykAzzb$$gz*- zWDY79FyD-w)7!~ja6$S6C&%i*!);d=R4#Av%!t4JWu+m@{`}V$?*GbPwTgc^&;WzV z2RpQ0Y)vv0XErt}o601Q52+wK|-*g zh;gZojz8NIqX!2YeAw>nn_oWfxxfNh{VAV~zopr09P=e AwEzGB literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_none.png b/arcade/resources/assets/input_prompt/xbox/dpad_none.png new file mode 100755 index 0000000000000000000000000000000000000000..d36e045f234a572bb4bf39f88236b6f2a3a2d5f0 GIT binary patch literal 398 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1F{QUL%?fdf?rYK3NF)%PPdAc};WZZjuV=wO^1A(^0 z>ynMUCpcy@TwH#Vo|KEEt0A!)8v1+)+J!aGw{p5kNkGCXrebrpQo#z%Q~lo FCIEA}rhfnc literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_right.png b/arcade/resources/assets/input_prompt/xbox/dpad_right.png new file mode 100755 index 0000000000000000000000000000000000000000..0f874acfe7541cf90fcd3c6887da2c9c448a4791 GIT binary patch literal 427 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O`1!2l#}z zJ~wiEZs_*H!1cKyknQ@+$nBY-(|<4kiU7F~HW~@k4b%t}%;s`P26BW;g8YK(@8{dk zUoU^Zet$ohvA>_eYU2lEp!5$<7srr_TW@bg3N;(>umo<}AiZJZyZUP}3=@J=x>T;# zHhs6&3VnSv6sQ&i8tyaeJeFCUEArSzugW34y)?_gX!#OWmSXOp15cRMG#7kjTq}~m z$_P;nCLi!K?Ebxd&7R2n*S|00xo~smr@By1g_(R4*yRO$I8X448)P#2i(klMG#5{Z za$rN!dEh(qBC!j*86%>XraG=uRU|C;~ z!@&X{AW;^#q~Ub@?z?Yo)@B`eST^tQD)t%+sL}&Z8^oVHU7Gtct~LxL^YlsqFv& literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_right_outline.png b/arcade/resources/assets/input_prompt/xbox/dpad_right_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..0c60966d78e3c8c3e6582a1d84479de95c8844f4 GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1_eEIeH_Wk=A+z*}i0E+(hba4#HxcBzPM&3gP0pOnvu72FTm_tCp zpd!Iq`%H4zNeMQ$W3z<0WO|IH1tNAk@G`CB&Jgg3S-{lz?8KhC)6RA=H%?_>WMbj? zW0UaohR1;~$D@80$uRBO7u@2$J?3J4zSE%5Kdt7Y2f@Y zpUGwa)R_4bPd|HX&wRt%Zlhqj-tWH*cIqigsrBj(8gJh0m+;!bsldR+e2ACf)5}s0 zr-mK#R&*8jDl^77m8zKYC_Fp%IQ;p1{XO6FauuFguqo)gKd<-x@TuP`RC^dKq)zYN z%9hvLc)YaX82{m0a#4X>LaZ8kKHa|>rG4f)!>rHB8@Dp@vMjjwU*3^7zu)c+!;wE; z4D%Y83oO_e=JF=kyED$&&8l!IzFwDM-bIFhLK%jyPaiOF^|36l_YgVIm&{lY&ZM)L zQB2n1;6;WjY7O65W-xY4X6O?6P$R;6AUWOA;X(J>EQ_5fe9V8DwwxARrSejj@r5|U zNvG4F+85X{?YJcWP4p@|M?xlnS-snZK;Sf~53@6$hZU7*Bz z!mg=x9=mKS*gu@P?_D)_#?1H!tPD>iBf2)+?@66FpEJ*QIQsFALH@r`os7cdtGBlv>R8o%vSd#$>y0BXZv;j@j#61=`m;{; zrt`wDt6wX62L5u7jJrGIWo+owr!L7sueIVPC9l1DZ$`ztppZ+m3a_u8bgd`fZ};`f zzn?yvl7IfZ-&UD(uiskg2k5-fJCfSqaB*VBswIrOR4#rA*`<`}bku6DX2y zBbHSkuys=Hn0o22+{&`)-B;(T&%9F9B&{=RHLpU3p3b$8469!qYq=hy%P{4T_g%@I z7a2mb*tOYEd89GcWGwSQL*&awb#R6 zehFPzu(Ic@o%gcqGgnW&_HM_=yXtzgFFjdjzP-J9myF%#h5KJ^y7{|2JkE7SRQGM) j{#pMM4MEWW%_{me`A2&8-b<1P=0yfiS3j3^P6nS2{ycsD^Yn%1 zMs7f}ORt^=IZB`;$S*kmeE)j;`~LCz^8D}X_s?f=RQT<|z`)$*>EaktaqI2u*OO)& z@U;3ewR9JneRnXZ`@j9pmd%;Fk4{om-gR4Y{(Zh*$Bsoz^1t%0d#R8ElLrH*0;5U; z%ZK?)COa=J`>nb-^xhxi2Cv!IDz8YzuH~OF`;elR&ptJWwHIH=^X!V_TyW27(YCK# z3|~IjuqZif&Xo0jyKEwh2KVu>z%E9Uw0}Q7mc0A@(z&+YuJ^OyvWbia zpDWf(W4KYd?7Ly*mk&I5blES=JAUh=_G`&i)0rgh|1SN^e5WS)tPw+gP5DRWm>8DE zr1k%Ug%$q4h_zu5n9tcDoWQVjGGjrijKftg#x0>d4$Sp`cQUNYRbsGGXMbR2*HAWz z;R8D-(~3Dd4XbA`%rtR0#(cp~iXn>S=v~H(3^K(Nx)~>y*cQTnui{A7;U_P+@fW-lZ!yWl7QVyN@ zjgMPibmX^8Vrkgd#BTC$@`D34yrTK6$4-k}NPV*Qpp1h1vj4d=nxyVcNIk*H@IcPu z4(G#qr91pzY8Om!c@q3reD(R#WOs%Gul7g$T_=5*A@oDD^kViI(=}FzZJ4}5Cq!A} zh3uY_Om`M+FJRliJJ+JHYHsjt1qbmHRY|P5&5{lq^&A9Q3SKkqV41)mbl^Mtk5Bp! zr!(&pozkooCv(5BiI>ibmW7pW|*U-I;uj_|*n z$V|(C84=Y={wLq2j#uPh>Fg_R;wl`r_lS{?sR&H!I3| zKL0qleS<)ck6n}C`u`DOT1`_|cWf^bWeQvO+}u#Pa;1&ks-A@9%0D@6XFq(u>toJO zJ-xPthW&;nm;P)`@m4qAw`S?*<2R2OZ=Ixjjwd?*Uf|(!o^s>UH|9utRm|HtEAwq$ zNSph4k;_lcnQy;t&Adcw?aixijvu}Gdv#Fx{CgeiE~koqUGas#8x#}JjI@W@DIsGW UvoLEXFt;*zy85}Sb4q9e04;bNga7~l literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_round_down.png b/arcade/resources/assets/input_prompt/xbox/dpad_round_down.png new file mode 100755 index 0000000000000000000000000000000000000000..3dddd32954019b7f1ba68dcf96d0c549afdb309f GIT binary patch literal 1112 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O`1g2Ka=y z{wDOC{oLL6#9yq8-)seg=mMsY2Ce``tpoL3 z2EB7XKTNt;`uNLv2DazZXKXmS`{6%^#oH$uygpvdbf)jizxIP?%b8{<8u31}X83UT zw7?98;N310b>A-GOvn+LZJNa}*}rC=>EqPH{fje&JL)bm&X|2WfA{UgcV!{Q*$iJg zU%m}u-hJdiowdWG_M;Qzq?EE(r7+list?avx9C3GgfH?Fm(ERQWbC;9|MZ53FE7?w zGCYdsa1ah)Puit3>*WUVikD&p> z4^Q6vGXMG*r8-MyCB?KKQ*C(4 z7jN`UX@=@}hS{tg-&s4r(KMg`!-v@)9x}-F-rL6N77^29cYLm%!|}R4-fvks#@$QR z?DbV{b8e|Cv^m1*wZHZ0!52Hs9Yv-ocb?p}?@hg7?*C2eo_2ChoHe)a`JV5eKW@=A zRi1qLa`w6ZuQRiYyp}(2FTeHwcVcywAGe7A6RkP(MOeCeTs8+WtZs9e9Q?9x$N$AK z%e5!G06zlN5q2wfkmrFd)0+*yVp|e|g5~hjh zMAbHgg%~b0y)MMy^YCbvo163jkJDvq^&GU$#T|D))(Q*Yt#;}D`U%&SA5lIm8ykr;nB=Jf`#NU?A!yD}4ch|=~jg6^k zxEpcl!imcfH+1I){okcKcmI90$YAZ=Mvu0cOTOnYFOYhmm&v#5W4d;&Dy?oum zz2uvgNzkl RMSwY$!PC{xWt~$(69B6=5)l9Z literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_round_horizontal.png b/arcade/resources/assets/input_prompt/xbox/dpad_round_horizontal.png new file mode 100755 index 0000000000000000000000000000000000000000..f19d5c08b7accf74d8fd58b2db67fbe55a00cce7 GIT binary patch literal 1118 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~q1o(uw z{wDhq~@xH}1TS$atGt@@46|&odUi@{ax3J?-zO&oAr(|2%#E^Yn%1 zMs7f}XO?b11=J=`666ibm`DT|LT@*U`+<|;0yJ8RSoGbsjySo}VI-uykNWhg& zv%BtwRx7*f*-mWDJQx_Y`14!Ft(uJ*OIL1ZF`1R~ceW{W7<$$lK*Jc z3=6Gy*O+DOd92E>^6lBx#cuH6|L&Mt)->4zH|-RhZ#y(NY&`xyaKo#2w{{mWT(T2( z;MQm} z3ktjk*c=PP9Sj&EIlDe`R!p;I+cCL5?GGcbY|dGUgk}HNePp{J%V5q^z+5M;QOACu zfsyS&d1I13!ySA}{@>1+&QMmP+_vx=!-DJwSN`YJF@8|^qW-^hL5D-i zE;&8%*Baan_NSf}?eKF+SoA^PD}Uo5#y!*J{`YB`2b zkUqOj@}q8@2TezhzIgsajeAFBec$}slRU)?5~C--7oYT(&#S+vL0MVjugJ9X?2-o( z(^{XNIF>SRy?gwDk5=a&N((1tn|-y5-dq>8-E^_}^5?1tD!)FPa&O*!b}559*YnT6 zWbV4`!FX))0mh?;1kd^~rgbXMS{l4dj{VD@;$02NyMA{t9@~A!@UGq6XA6wJ+G(EM zZn^60U;fqB6YO=}*X4>l<||QM@GvGK!n%OAVVI+?5Qy)X-I(JN+b+(YL*d zrW`d6kstE?ichY-ZS&;Qu1U-xiPyhJaV>BbmgwO*(6YQhg0mrV@{vWh&fZFM&W5F4 z+;Q1MsQhkA;C1N-B2)gEzw>_kdr8sMhbv>=7Bww*H;P`k$Edh0T6ObIap&*XcQ5t2 zdAXnQ#&t!`-nyS#?`h7x^UZia`)~EF&(Gd`nB85U}*J>F> literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_round_left.png b/arcade/resources/assets/input_prompt/xbox/dpad_round_left.png new file mode 100755 index 0000000000000000000000000000000000000000..a031aa25cb4145db6259810ad9b923f363b7fa05 GIT binary patch literal 1125 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~q1o(uw z{wDuN!wh)OEj($atGt@@46|SKhIoXDs^IJ?-zO&wrjie_JybUFsyxd|YU}VVW14K*RstN!hFq?HXSEX~@atmt=9<^I!aM+i~~u zpA0AdI5Es>U@BO^%%I1Az%Pnn@>xcS57*G-#G%_*N9aAZNn(ft{0i!vb!G zFphvs<`5$W{#y(;)FfW=o?vsxYZBbiaP^)cgOv3L#wx2DR_S+QuCz=3WI7A$5~a<~ujZlOPm z;d<}eNer*EY+o{!+|b;^7~$<$9oul#f2PWTU18mN2l#fgnKL}z#*i$;u#FWQe+Ry^ z|M)ch!)eBAGk&;=FF5)r>5Ao@%Q zvw-Siw`EldqS%Ggu{j%KDm!H>Us4g$^TK@cfwbX+7 z@BGidWIFn|lSx9WmC1FHOYt&>v|b_KRbJ&0F1Gtz=ISl>`CfPGMPbLO66?*r3omWZ zURpLgXzf$+)xzLo`fZz?I%v@QhX})d1=hzrYYKTFU!^}T_sUu zHEo_jkz`u?v-%uSaruPn&p4zie?@Iu5^S}9-ICAK^Cxvi-4sf1T(f=d&dcYSN;iAl zxWjhti&5d$fLpdECEqwhl|^=*KKiz{-L!rAg|Cm!KRx+-?dSR32A|)|J^L%uN!wh)OEj($atGt@@46|SKhIoXDs^r>GQ|#X@8zRe_lG0JRB}1o;K8KcC;PAMbC^e_#In{QCV2J`D4e85o#*JY5_^DsH`<{c_T6 z1)heD8xOKScvq{^_`UwL?X9#+Y$t`L?Y=GfMf$Kf%Zr|XDqpQR#SQN=ez$HuJv+Gym-@=QLTU7zwU$n){H({g08;S3>T(9 zR}^4mvsIsVrfLFSKEEKCNIruM$eZufa`^9t(4Ka>x z6$cwf!=|Ql3Hsax>@UA^IjoJGW7KfJ%xf)Ei|G300t!yE_A*I)XO#$NytI#DB`69% z{AJwtNA*t;1AA5eYz8@n{Sqd}*XkV5z9}PpA}fd4!eQab@5Lv@+}6$KDL7u>rGMvB zLJC8kpU!p}4j-orwxSQ-eJs)9_t6OxS=y3zzCCxLq}-gcY=-Z%n}cm^=Bvv*c$r#V z(WkC4i;>yl9+!$nSda;j<+&u|+NP3%3sr(GlRlTQW?Jp|RZuh0^RjN%e3zw4>0Y~Q zgSN`rM#@gA*(UQKtVimFTSI5?h6|qA(BZUFNf>e@-0QZPuIqLuA1PVktQt_lEHGf-{pHFA@p1l@Wvi^Hk&OaOO z+-7&*Rq19sKhM6s{2t>vuMeyzk8b+*@7#}qN~Ku~-Ug|Cl^28MB`pWm3g$^xXC81a S-)#@fvkacDelF{r5}E*Ly8?Xx literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_round_up.png b/arcade/resources/assets/input_prompt/xbox/dpad_round_up.png new file mode 100755 index 0000000000000000000000000000000000000000..f18f7189571a297731bbb38eaec7de714a9b2099 GIT binary patch literal 1128 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~q1o(uw z{wDq(4)RMoSKL2_8{FQg?3%kJQ zMs7f}`TYztfZ7B~g8YKlpYPAtxA&Kizt8`^e*S(2vHH^g3=GWEJzX3_DsH`ot4=?X|&G!2`L)G)=GdA4Z{qG+`v$>$* zh*bZ-pTnqQTcyR$a4ntb0gFUKpAkdgbjB~6MGoAV&JgkA^?BBg&AJR6fqV*Vb`9N= z7(TFbGQC)=l`ahh*`8C zM&ZOXX67l%8YiMB>l|Qt`Yz#-Pdd`YN{#v;MxR=n$InPiPvu z?&dvwYc|Mu_&8;p)SG!r;FR!W#_TSkTLEI(>w|)fO)O1sek;k}rnbDI$}WA;)53f6 z<~?TZV!O+Fn?ppyNOhKjXM@?)jG|Dha%7do58f}#+NQJ%(}fT zJC!l2?7qNl7Q2RxISflm&#*hpI_~i~>NKOm#<{mdZFFN8);#OtlGww;p!Mj0s!SXc zgZM;^tf`Xxx)Te`fD3ttnM!NLD>K!i_b}w|f;IVq+1l75BzNV_qy?-Hj@p0a!!1E|73QjsW<)qgoV%Cv;X?L?@B%#e&1b# kuhvI|G;jq#v(^LYYZuQ-ySlwq17=?aPgg&ebxsLQ03f0h7XSbN literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_round_vertical.png b/arcade/resources/assets/input_prompt/xbox/dpad_round_vertical.png new file mode 100755 index 0000000000000000000000000000000000000000..b1c36ae9a02d25405346b8d45e9a6de19ce54606 GIT binary patch literal 1128 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~q1o(uw z{wD>xhgGb=|*i-1)L}-P_cXSKhIoXDs^IJ?-zO&oAr(|2%#E^Yn%1 zMs7f}*{UMgfZ7B~g8YKdug~xI*O#}Cf6srve*S)j3X{oC85o$Sd%8G=RNQ(y`}L&R z20X325@(L8&9;`-tNXwG&X&!$%h_Xd~NP{|Axep#lnRZg&iyt7=#>{JQz3? z82>D1SbJ*g%lg)fJG1^vIjr(iZ)3Fhe0#U_gFI#q zmg@iWH!?ijw~LRR;gd0A0TYh{(73QR#wor!4a;~KGahcY7e7#z%ixgznDIsr|AJf} zh99jgj8n|?8gyqeD498&XTD%&#;}F6shX*Yan>}(gbUHO3JvVPHaGDFJUn`hhw%)f zx55wZ2YT)cCLQ@M!tsyEAwKy)HP*I}H36J$QSdB;KZQens^Lwty3#_V;$1A2Uyp)?#9)VAEq( z{OjNH(8->iNz-MQlh(Qi8|zkTe05LHB9A|<-@Pra;a0E% z%dzPP7VKe|b>}*puH!q_69Ey$%5{h0OygnGdS7B$+>Ol)ChuSbT}M;2-1vrJc%`Z4dZL6tn7fti91hN!u_ZQ#A)`orNATqUhLf_lMGG0Z zzTFYH&0@!3V~VnkfUk<2aCfzM z-n&2M-+ujOykj=0{Mhvy*Y8#I`sg*C=Ml@i{i8q@kJ1(BzVd*ruhEXWOq^`uX_w&h#wR=sA+x;;sf?{?60d zothKYHfdh^y0oa>w__gMbUkOX^Yhuap}7q~OASryuZX6-*I)W>zpY^H`c%o1)nE9# ek+M|{!6C>Y415pmU7B3Usx>{!wxhJ1b)~asHnD>ZeH}RQ2J|w-Tt-V%Twlj_!!7l}*sw^i0Y@>(C@#4%a9H#%{3XDZ{rUNPXNkOislQ3$Rhj*5@8wt8{k$-1-y}vP zYaSFh94+e$ayVMx10>461vS*NTI(nLKmYdhyT7+3K9pJKzq@~};TTL0ANw8#V}Yfy Ty+7Xdf{gKW^>bP0l+XkKBVDgY literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_up_outline.png b/arcade/resources/assets/input_prompt/xbox/dpad_up_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..0aa2b5779d035586666c72f8ae3fcdb38682cdb3 GIT binary patch literal 394 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?I3?vN&YJLDImUKs7M+SzC{oH>NS%G}T0G|-o z|F{8A2~e_Mb?rSMi>oBaFZh1_eEIeE`ThGDw%p-Y1d9Iiba4#HxcBykAzzb&Kx<-F z-vhxNjWP!gG0*T=%j(`BED*K$2wzxdn*YkFk!5A-#)eYO?@PV@NOjjc71b4U0*y%H zmnrM5T^p%;^}r!Nvy8@bRja2sJY-%hbfV^fM&lLsD%FIOKxGUJ2kIFo%X}_Qo_nO| zO#T9g^w8BD0WTURGR)*NVDTt*@NalNz4~S1*1~_G;uGelfwesN?4bGguO`cuobx9S zTQg?K7AQNcW7cchvzg(n^;u)~83$U6Voo={4l7DAlur$p0&DuAx`5|-1jm+(GTe<; zvyv4jxEKFf8~(U%P4Bm-d&INVj|Vwe0TrHMt6<>gEDD|WXYyx|K2KLamvv4FO#nJ* BqXqx~ literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_vertical.png b/arcade/resources/assets/input_prompt/xbox/dpad_vertical.png new file mode 100755 index 0000000000000000000000000000000000000000..123229ba99a2057dcf716bb79d75b4f649bdb580 GIT binary patch literal 422 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O`1!2l#}z z{s)8SMsCjy-GIy&1|X5=hG5dj^_dY!5D1<7wtEp!8=?7srr_TW@bg^EDgruwD$f$Enho@<016 z+byY;F^;Qe^8Wa9YtwE0bBsX6AW+A@;hWvDPm3gK_rGH7|Fp4PYJ=!vUv7!+n;NWV z%o3(CMM+=qVtncVRtzP6Fdw+oI_LGtr!u>qN-S8pX_j(BNUN%}L1^Y=PKhlI2U#q1 z56o=P;zQQ+ojv94pB$!1ViSB0EMnF99)Eu6?~esjZ~D&8UUfo0Oa1wR!gi5Y=}JYD@< J);T3K0RW!Utq}kK literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/dpad_vertical_outline.png b/arcade/resources/assets/input_prompt/xbox/dpad_vertical_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..d919814ee83910670b75bcca1545cbf271c97f66 GIT binary patch literal 376 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 literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/lb.png b/arcade/resources/assets/input_prompt/xbox/lb.png new file mode 100755 index 0000000000000000000000000000000000000000..b7b55df791a184830d799aabedd69c6855570d3c GIT binary patch literal 589 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDgPP#Q1=b=_XT1 zIRpEKiG~a6{$#w?UViep;Q=q_hHG{?%%3bCT+Y117Q%R-J-SkO!vq$Eqo*14nJn^` z`5q_}{7}BX*0$o^bjITf4bL{eozY;ywZM^~mnn`v_GOtvy!B;vi{t|?#~AW?D_pW2 z40rRiTPsGEJN;6h8}MFWPk!}h&l4;%#~6;f9bppL#F}En>SO$pA+O4m`G8D7>Fe#w zQ`+?w*s7Q8Vle-Zry*qjp-gAeQd{|sSznctW7bV%9J1Cu}l1B(I!BZmW= z`A71=wi_GvD}^50|2`t!iZMANI9}sApTRZtrikNa4Ksy8eRLacs_){s%CJ@_bB9;M z?>|4YHl5bantt=O_U8h-!}*~Mzb!j`&nKCyAH2qJsQOJw`M$!P+*%Cx_t)RhFp7)w zQI>kJZhC%c&b~b&SABnHE%y55F8;=&;oFB(xjjp5IIo1(FKsNkZ!Oo!%yZ>$Jm(4F z1l8RAOcU4-ygtElLNcMxr{NA`^%JHib5d_G$emz#%06fQ{`-vTm)Rv2@*QA&aI=|} zMT7MrV{7borU$2Yhlwuu@~W9NW5ryp2J^EGk}=Ju4VU!VSbQER#4+zP-In>LRFg>2Ec7&i(toO68Boc`eL=gb53h6T)SX$RzHG76h3Nai^tM7Q0Yxxt!0 z|o^!|+f@7Fdn8?XM;>;#Wmq*$(Dv`9QG|J@-jADE&TJYD@<);T3K0RVl7 BLGJ(n literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/ls.png b/arcade/resources/assets/input_prompt/xbox/ls.png new file mode 100755 index 0000000000000000000000000000000000000000..cb6eb93afb0ea96d3939ff7f01499e1da5413736 GIT binary patch literal 971 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD8SHKh49x1DE{-7;x8B~4 z%sZmM!w@jZ;fCb>|66jGzLXM^F1Y3OWBbJqA_@`(M;}jj);YigL-wpJ+A*KxC(haM z(5#_Q(@8C|&iyC5NG zgJj7!Gau7SFnn`9We*h1G$BR|-NJ8c!y6G}M|UEN7JQ;df^|Gl#KvroxVC z3~v@qV9iM2>}6Atb2!8_BhKNUXh-!jhf1X<3=_Ep3K>{)76vrj5vcheo!X-L`;J6* zw)gK9Ele&}4|WL(3*KZ8Z(y}LkPvR%&0t~mz(suuALEA<2gQ%~0y<% zJARkrgk0CN?cqKQ6CQb4>|)r*|KVwL`IRE=^ZY+#CNJckaEx*5a^b^8K2^K+%B=o) z^+NbA2g4;P&yHuka84DNb#RHv_X0yz>s^yBRYlEX7U@5n8Xxs;1%t+tr;Cb{lTJ3& z#LbK4GmPHszJy7kJYMG8y|bC6)$`botUhq!-`kx^%;m^zD2w0^nL%Ue5#`Dv`5rZpR-QOCU39x(Y$^@+|y6vsaDF56*mN*maM2cbj0wF zCu{Gv+5_ zO^X$H8k)NrVs3q}zx8%raP(0Dp@nyEO8(p#{gAmx`DEj5d$|I}7zXhJtPrZ6OJJ^% z-JUk{Q%Bb`>Kt2cZ8XXJ^mE2J^WPttvG=!1gXxo3{%pziED6W0i+jE?GyHj)%Hh*s zky>Ql&wsF9H+Ld-zJkW?Foo#Co6Y!PDF4`44VqSo3}U z7hju_q?3#ruGv3)Di=4|)q(NADqr>3+L*86=NQ*mTkQTfyTR(9kweY8`ETBS+3;HC zBa1`E&i{Wznq?A99eiKgb7Zk`YRQ|qpAER_k=eNXhtJ!>@0X2vj2N8jRi&6KIUO<` zx|w7`7B1j)*vU})+Q{UoNB~3hW!Z-nSzA(>f9Oe71ZbXT-C*OdC$X=FB_NFPHvfTX zqKwQQd-)S=n~pFyoD%N+Xn)?kXCsF(QyRffQ)lFffo zmwA^LuoUb!cHulwP%|$r%;w*})yWJJXE-i0$W<#;pOKJcJMdnADucsg|1W>9tV)RK zXIQ}(z+m{0q5A!15w(VrhMxv}Ju+2I<&2Xh6qtdtcS zM44pn-B2{L;Y zI{T$p8) zn`|7mPGVu5wrC}zjo0bS;BB$9f=pho_rI>$mAH8N(w9E=49eb@*KS)IJ@4GTkLi(u z2`}fYTw=7|LA5+~{#V(lRcx=$Jku+ZNPp$V&Dy>4v1-MpA512t?Olz_{5OXfH_5r~ zmT&Yc`MNqnboQ;&)9f!z(YmU?qVwDZm5%I>+jPza*zOi%a{D+#!^i38%JX~w?~MD` zvWwMTw{txk)2Y=AuY0dvJKzu{{9x}VzbOUk*M4nP6T5uxNYKYu2R0|axwfJ7*38?d zLgt2TH#G{)wOl+k``LEar;>|zI%{8FCi!f=L747mv+%F8oQMT3GzLWR$6_FRqJ(Uj}UUza<)XiYYF?hQAxvXEQ>vQ9-b^1GOh}XIst@&OvP%KQJ>6{Y-Ba46om^kp9smcGi_S3X| z?IJ}bN1bAhI3OM8m^ODaB9kmCwZBCce~;Kprk<5WPg9l(8!R~`((GaWpl)vdTDkh5j6JF+ zHYWawTw46Pjz28j^&PXy7aNwvQOozQ{u;I4Y!1izSL?Vh37f6vRA^ve0(uNc{9sc2 XcEeFt=ffgk#4vce`njxgN@xNAiP+|# literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/lt_outline.png b/arcade/resources/assets/input_prompt/xbox/lt_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..8792f4660837f21c30c7328946ff6ee03cf07c24 GIT binary patch literal 722 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD#k^5Lkb(PKnCH*$S(h0K4U@c2o!6PjlJO!{beepW73Ymt zB@PvvbU7^+UjE#mX~%Jo;rZ%SANPyr&tzb6U_2DWXD+P|a?gL}hX0}n#~p}gNU+=K zaA2qXfhy*0bqsR%b#mD+eC$0`*1)^dH=MU1E@l^_*n=cvhJOF1vS<^Y-I1}J8>#?jYQ-<3)oQ zLw!uDul@lB|3)>2cLz)ta2`0{aN>S|^noN{^^H>*SNk^|2%NgBp`d>E7e*b$uA)ML zXkmeS`yW?}A9($5y86Sf47YZzR<&S^=xV>OZGPyx&4F(o4Ue@sr)G5?4S)A@;c-ry}Gee!-Hht}Q*H!(~*(*#K65ZgKf55EPcEs=m$6;|`s$%eT L^>bP0l+XkK@=iti literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/rb.png b/arcade/resources/assets/input_prompt/xbox/rb.png new file mode 100755 index 0000000000000000000000000000000000000000..6582f391cd6050d2deb7cc79889774fe751c69b6 GIT binary patch literal 684 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDEaktaqI1^ z+eOU^0&EG71X&92|IfUgGRtg9!-Cy6&i$!=KH*4yOpIUP?V}5UhM|Fm{oEJIV(go> z9{-ayzc+0&!|jVZ4e~4)C3p;5et<2zF^Kf}pgX*;xzq}4Ss$_hUE1RJ5 zO=2~}Jb8z8Oa>ENdY(2!vN&v$*bo%mn01j=?!8kyhr-Gar;joOg)=|8yFgW8R?D=8 z4ATs*4%?<>472!|tA~+3GyU54mkgx!$Yfzw|K7&Gh!uSsvW*{8l>0yy72wud?mBEB=w;L3M^~Qtp-1 z*C$Ug{rELg^_E*?!$U@v>-o8#we$9yZ)Eg(zAZ_!;(u(Q!y4gzy&Ni>FG>PFB{z7o zeF*EQQDa@k=pehYrH4WKy}b3*34#ei`5ya(jF_HHl2YV+A#6T}HTc7xOOAX}Wrqp^ z<`*}FidiYVdc8MQ% zIhc|8CUR4-R`fh$*CgO?ND=C?qPrknlIlAdx4bR*Dw`Kl$SFZUz zIMtu0-dH!m;*gI_T=uv22PIEG{!NmNb~QMs_k$gjqR_yEF8-s7&RJYDee?k& Jvd$@?2>|7+Hs1gM literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/rb_outline.png b/arcade/resources/assets/input_prompt/xbox/rb_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..e8a78e81d10834dfff6c8a2ef111782756167d16 GIT binary patch literal 800 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wDfFZ%gLM7(?|1G{!zDM6_9htht`1F1r*ZqyDmTNlX^_m!Y5*U~z4luA8G{BjE zVjGGMzl{q@t=z)dJo6mW9FON+ymvVr(uEyAHdhH7sI4+uRnDMzQFQ%lM!kt*irS2m z<7a)lB@t}B_MPt;gLTd~c3U0TuqSTr%zIh3y=__t?l8V9>6&Q2QACU3uFO7>$h=qm zza0()U+_2S{5CZp#_)mLb;kR175G?~_!ynH-#Rn#L8{_|pUHnuwj?G?hek%G-iLMA9X?P z*OxB2sP|$8!)sQ)8!bE$R$L_w^CRbe(=uPZMxABPHSPlKAJ%;HL?;O(l)q}&x9{M? zK-P$|+S+U9E4hV@?v|}K7h;Gz?ajY!f`?iq+reHIfwNYNw&@BqG*&;eb@)~tynN#n zX8V~u8#MoJWk}#KTe@3>f%RdUYezWKlCwc}6@FZrYJ4|O= zo-nj-Ibft{=`j6Chc+Wq=0bg$rl-@-9$Szo@XWnpFT25XrW7+aUg1EN;|p>aG8iA% zX|?GqYcSrF_!HGJVRc6^L)W2(|4bh~Ft$CI#d2?r()X4=xd&Y5S^r*q`flg%L;nO8 zc&^V14^Q3qAxA^_ZEWs58*abj7nf--I-r^|yPx@+|*^-@H?g8V9?$>Z0%`b i|MjgP~sJ z0DEF^O~fXpAKMuvX2Rmq zLxvnR^KQlkdl=`=R@f25V4^9)n3875$>w3hz{z!B3*%E=f%iL^zIF#N6~r*`u`Mv3 zohfkOCM!R0(S>g^8k70HAB$)Uc)eJN<3K6HK8>W^g2H?iZ0QXX6dBg9%UW<#hk-#y z;K57r@&d<9Mg~Ey4+Yb_9`0@8nr!UA#$0iA&Gs(IwfqSW7`}7wPS_=Nne~PsLwsuY zu_<4x#SfSy7?r$yA!@3vIEAB7fEY8*OTIR0v&*!Rsp?9toEZ@p4z{p@< z_y14u1qt;sMiG_=Pp$V!{^^^%SkJ+Yxg~g&2d{`!!;e3651D@1{g}1liv?P$*rVRL7y<$e3h*%kfoGh}ApM^$ZPlR@d(Go3!g%EZum+f~j_;O11CHhXJXz z>4#rPS6|UTnfF)MHpg+RN_1P-rLf3(-O&NE?W;F#y7O)Bs|~Dja_S1bo-deXwk?R6 zE`C{YiD|aU@|~Mlmwr#$-)pwK_nS@lwgYoHd!+Kun<{LONj}K;>Rcmp*JsH&jC~s) zJf8dIxai&+dc7GtmX$o^{;hqyWc$?gzoIsprZx<0c5C`%Ub`RJ60`3#^D2HZ?!Ujd zUmZQ7{p#z~<9j71p3gS@b~CT?G@}Va2ESwFpVKQB*6cD>?!P|kKBLw7?PGpQz9~?NJr9Y~6&+=dtoiPj?AS6lt8YI_TkH6J`cWSr7ji z7I);C3%He=+-B?((|PH#+&;+jd3xTV&ZvVz=`Cx_8@#V}1eh;wIm`H@*P_rg=vHj* zZ#i>+1=n{Kg}d)$^DF7S75ZD4{HbnnZq3IIiM3|V@}~a}BWI=m3>Wmzh%M~A{{)y> O89ZJ6T-G@yGywpsR^Ju? literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/rs_outline.png b/arcade/resources/assets/input_prompt/xbox/rs_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..7ea3310ae146948d1a34640f135925c3d04cf1fd GIT binary patch literal 1354 zcmb7^{Xf$S6vw~YjE%-T)T9>&T;cDorFh0r8Lmv42mSXZc)N|DE; zOvSipCc31HaZ8x86t!F`YOB@F-R(cPpV#Z0_c^cEIj_%epZrkTHk9TXO#lE;RDa5L z6~X@s0aNvl(&8^Fs3mv@djr7jGg{wb;Hup*G$_n><^M?aq&i>Sa$K&O&z zhtEt$=bG7-lXSY?0n*NvlPafLD#bhO@Z`c}S&0O@3hdTkZhrkwxj^;@%^ra(Os;dB zi^M;?G`!WQWI&$VIgUz$O#v>VA6T&li9v~q-3-E>Tom){Bng~tnG)(SKPt&%HwIlG z=O~#Z<7%ZIcnQ->thWQit$ZIWblh^ZBO}Y^d$z+CU(QP-cRer~L$a%=y~q+`mtuej z&M>RLApDFvSu-PB-TKw~^8S1G0!1xSU^8+xlyYpL2_^cq-2QrUa+g-xNV-_}mc$4m zkj3UT;Ta1?Knbge>9hhBmGMRiAC!TB)g}b%1l1o{Ir2)@kF_^DZB)lJ)%Y0uqc zmppJLWCJc&h$LHdZ>R!HxEd#l7d87LFZ_fgqx#lT{Tr>D@`d5ItK^~yc5J|$E`Z5r z7FJ&SP;uJB!Lp}hbe(~>v*n&(sx^=It^z^PCb!hg5`wS9))Y{r);Y1}(LqHG6WUy0 z=n?-#(@bLCE}G&rV(r0A6AZgLjxF1mydHX4uNUPQdBF2^zGh9Kr5xU#<{n9e7c^7O zLVJbM35MKTa%kRWq^v7taYvjjzr8UC-v{Y$Cp?o$@}9^Z!-PB*M9_Uc;O5dm(@dku z-LCE#m!3F*_J~hMEPg=j%)uN60>k@WRb#{s=r*TxPYbWgeTphE=3Nd;awsrYLfBXv z*AacQ+AaNp`PD&5r`z4izq1gC!9CSS2A3m$dHaPwE63YrzmE?-s)J_IPwH-L%LE~x ztdjA%PR(gXqu*jzO&)lXZul)n4*JUkx$^5^?l7un(1Drc^2EbM`Wkp+WW$B$v+_s6IF_& z`Irb?za@?$(PIfzpIdR$Q4V8CYcsMViw7f6&ZfZ=!j!glcC+M;$@DMr=e;^|+I~h} znGZ{(my23{Q>5;4%Ut-&|2dC1wbC=A-UdklO{G6TYTe)Vh@sHirb;t_>Pw?EZe&I3R^_DQCx~h8rv)4}%(*{22GiZ(L{R&%WHoPMZ0_ zGL3`-(_Oh)E4UIGj-TUX-@wMRL;tSB0kc@19qU)T{5j89MpoTG(DuHeT=i*DmLGQ> zEN1w6pP5T)LlXPLCxQJBuiZEt&&MIkqmkgSF5*DI#)goC4NYuo7!JG*zma>;wIOvo zt4_m8ThaI18dw8vHQW{3z#x=p$}`D0QH|kS)B&asQ~o*##PBk+Zs5rCFuOGG!*j6@ z3?9L6&g(v4?5JC_jDbl?;m_JB3?3N=y4Z5qcu!8zopr08l0`X8-^I literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/rt_outline.png b/arcade/resources/assets/input_prompt/xbox/rt_outline.png new file mode 100755 index 0000000000000000000000000000000000000000..862cbb2972da477462066db04f2364af59f2fea1 GIT binary patch literal 824 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 literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/stick_l_horizontal.png b/arcade/resources/assets/input_prompt/xbox/stick_l_horizontal.png new file mode 100755 index 0000000000000000000000000000000000000000..c513f8dfd42999030cd653f5acf589e9ab236bde GIT binary patch literal 1257 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD8fBt*@`27qj-uY)%}`<~nO6K@(=D_3#2=d@AAOxZU4nr*fq|!i(SU*Nz<@GeH1vKNbI_}|uUcF_M?%dpwHHovgYTi>tx z;WIgg^Be#D)h|f-_LkwE>D)CF0^;K^N*RljK5)*b9?Wh z=ilS2OCCIyIrjG8EGb4a#oLbem$#T5v{nmgUvf5C@urySoHgC|f6INjxl&8)&zs%< z?(xaW%FSX~Y0vo8{hwNnyHu!(`DDgLgPO0n@h9;B5Ukp5zb5Ah*P}5=zdin1^7sJ|b?^e1e7&BPi zJK8?ygL)X3)uT9HHue1sU${c*z~k$=I}a^uS$%ZrjV}xmF5GM{P5W!-S&{sSvb)+0x< zfL_Zq%=|xR8go)okuJk;rG_p2B5I6=4`t;Qw4Gjrbt*&~M`oJo#x$f_O;3wh?Ka`; z!aw@+uCFhBmC%+W%DKvRWxu?ZUu?yyBc@k3rSRy~!#hE8@`a7K`< zw9hF~?Z%xY{A>qy9obwTQ@ObL^Y;GJx41qO{$f1Hwe60|9q!wyn;=Pk_1gp#PcqjZ%Ianoc_S!OQ~UAkH*qD~6+up0E+33qmDbc^ zmAL2K&aJus_dSnhn(+LNSK(XbMXIm+6@yk6v8nx75N=Sykn3|RE8WF?Zi`&!szOoK z%|8xsT-EgI)li)y?EE$>?T>MI*VUsDZC1jYBe>Q|`RVNNa_G5gRP3cMs`E5VP+!7v zSFU5UNl5w4@U=f33d(1cFHEk;{q=Ts-^0?=E83*px;$6D+Z@lc8^|7vA%MOvzkx)esxSueDt&U@TnV-v67Y344g_|q)kbaX>iXv49u(&T^VwW4begq z_!?HqB~-C2_@_H*d6i3csz-w8mp$LR9;UG;Fo&3|l-b<#bln|Lt?o+I>23QRt?RZjZ&q5G4V_57ti6!*9~h=5;Xufv2mV%Q~lo FCIDu7MBD%X literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/stick_l_left.png b/arcade/resources/assets/input_prompt/xbox/stick_l_left.png new file mode 100755 index 0000000000000000000000000000000000000000..1cab90bf850e5a168de5e7f9685175be39d6f768 GIT binary patch literal 1263 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD}HhFuxIEGZ*dOQ2} zq{kXO4u;Q?34IU%#n_Bf+$?VCGlOhK}6#eC7`}q(91SV34d8`c<^Yi0i|}0)B_vr@xE!>Nf9~ z(|Wa>fyw>X`~DxZBKw(HtWzQjlQo(YR>w~&`5v~D*OaYb()(4L`2#x-bG5A6`e}_# z(i@g13jaau>INF~fpS5(nJhyY{v)9pPbUvEiCWZ3%w;caw z@dOBnES)9GxPkH1>ff!NEONZl_ybPG8%IfTY%wkFihg~J^~Ncdh=)&SI?Q%{{+}go z|FwCS?jM?DyVB=<*mWu>p~|puD>YB-0iS-Lhja;d8Jtq%CkMPE?%+yG+F!Z zOy5<%ejY8;X6)T^Yl5AxaKim{Q+x6<7k|C}=)7~J74L)Nzib;sefN8IuQZq=4Ah?;ayn=LE!+yNH3FI8@vrE+|vHW@W7_S;^( z>*>KucXTAYeRsPotaO?yd9TmtasQphqn9L3Czvf{y%j9>XQHn*W1-v0$w6#8e}p;T z?&n!sQ;OZkfjz7reJ(RAXRn zcqn)`oZ*i#=K+fYUhEP3nH=i7TeiMjo5jz`^m{r(N7jSLXrudU-kGn|+1&GV->tpX zkGNg#Gi8)<$3A&)_NA%7$*6CNmR4$qPepP`L^0pC{vTVl{=N( zeCf-EhA~$mR+)TD*D|6^cHPx~aL?m$&g-1VJ56-mn{Xv2k*`Y&$4XXMj^-8nGCyV(CnpA$;rau7K_?Xc5xDe904RNm$)E4 zZmF=Ygzzpir&$^PC27F!Q`#&4qHalcHNEmgbs%xNE_50mlP|C<`Ksv0zu8Br>b>!X z8hN{L_4I7t@{H~v(*UB@121(db;8V3si!d`m|ep4uDCP;YDG`7KiW`!7%$CA-W?8_ zsl+^z;@)Gb42!}$jQggasuO>?xdjN<@r!CR1{lh^)t z`#bG_e$%)I#ZtS_XFIFt>SPGD5XGtdgF_T9i8+-q?$IDB1vo!Pm>+F5y%mSyj+@!O z+Xy%0i;fuvfl@)S0Af9iT?^nZC;D#44Y~8XBo{5yKM}CNx&<^N;Uf;ZzTnyi43;VxLwN0R8lpFi^Wwe8E+F2N9yZyJ^l4&ve_r zLPd9VE(D7>lWtUfaxu%2C_>0+pkXDx8nJMPgH8YN3ARfKm$+YVBICMRPTQkT2oq?bAV1||g!7j!wcA=u)md{k1nyxkg}U|bj_C3z7y zT<rp~3D=50@k}Q}JZ@zw2Xb?}| z=yk=5+}z%6Awj+$WIJ0l{Y7xBI_*f+NYm0!xJ{9=1i>kW7}^_rINHjO;hlWVC1Y*8&QV MNg;lsos_))070K^b^rhX literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/stick_l_right.png b/arcade/resources/assets/input_prompt/xbox/stick_l_right.png new file mode 100755 index 0000000000000000000000000000000000000000..256df0ce0ad555f44a87128a27e37eea478a05c8 GIT binary patch literal 1248 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD+S5D zla?y*I54uN1poh^y?oX3{O(x}Ojh2ad(2qdXV`8(`fd7j2?pi_2A&2+0|vGO|2ZFo zT&g-d?V9IKdu9Xws=E`nzRIim)k+}tR2Ro{cJeMjt%s{PrF zGmItMA0MvjV*bW{C}^t_k3#i|KaEQ5OPrGr=gqQN$o53vGEt-E$-4bV7lw1Us|h5q z@pMQ|)K8lo(9goO#{B2*snT87C%=olqTaQLwa#uq_6d(^a~Qs#5@^^zkMVB3)ef%@ zk2QoC6LuXn)KFc;{DiHcI+fu-jHtu=T!xwKhaWc`@I6pJ=lwmd0t-bOqq~epN*Cv| ze-Jn@(_w*GhU^^CLWXagOcsK78195JUz2MPWjV2Ux5MFtJ(Wk<8fx3Vv&uNc+s%K$ zEO2691JjJte3#i1Hq9TEN?OM61fHzrj{UsU8=(8 z>}2~6yD=JMOn{ojEN^s*UTNRES(-}Il4rFT>bf2r5e5%O*iP+u7AIyukGj!c`n*U?D>EB&; zjY%9UpRT#~Uq9ebish-SmMoi$KkY{u5QRV;8q_EQ*-QC6bAq-Tz?E^SsaNoaemHbACIgy**vjR0*m805z(s zlaHLNf29n|^TJ5+vK)$$4jv8w@C(<;LY3sbh4%rP^VaKbNM`Zn=C}(H?Y+LuY=$BGgmb5YPB=KC6p6ypM jYRzT?2gcL%-!MeZ5lgxA*yaFVv{RY6tUWR*fWBc z3|m^-n1se4Ws_dDbyT z%zd@&tAM_r&0P>Ri_hp}*ukFk(Qs|>cBvDueFH04`DyBz5H~0+F%i=tqt7`ya3`-Nj@i$8hMFgq# zlAe5UE6=l-`-WBESA}Km&%3t^3a5SN9s%ZVe&HGj>nOnBhS*UGm!j^#G;U&M40tvttPmtVSsU?w?r#Hv_WEVPnOQL^XsgJWI)gbw z#JFN5jDd7S`)k@Vvz0>Z%4Qn>n)N?o5h_FVc3T+Kh|X=X(sy@r-&lg5^BGOxplhg# zH!8L6=+p#Z)w!?TVpCq>md)ML&0L~wO&`;A&*APIGTh)U&BBzFPQ;^3cg0%r46{0& zrz*$q(Iotbrmn%;Z^Eew1V7GPliq-*Qv)%e=<>lp{8KlrXmPvC$4IC1D?w+k1yr#G zr0sM5+w54IZ?xUY^>$6O54_g@w3PVsw>uO9u zs(vaz%hrDsd(qZd)xOD55Cjzt9-KQr9?r1VQ# zYJbN|_4N;K;%jIo$%2f=>F6PAO6+}MTTL40SK4&r@ua3W&O;c!dMu~%x$w^>Mnhcn z2~HTXWH_l>YC>X!5R+CGvfoq<9;ir#PwTlhB|5xE!yvbQk|yNgdA}*w9(l$-wC8Vg zA_}aEi6bZkkUgEgtAjm8U=lsT1gq+-gmTpa1Pz(?8MvpHGhiiOE zes?2ZdGdCFjak#hx~xCOGSY{alm;W1@hid+tp;DMR%6E(f_dkqS1Ef$YE1YI_zyCe X9^6trG~2Nve^r3$?CEsNF(l(3%iCPK literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/stick_l_vertical.png b/arcade/resources/assets/input_prompt/xbox/stick_l_vertical.png new file mode 100755 index 0000000000000000000000000000000000000000..7b2e6aa84c83290f4af578aa979589783fdf1bdc GIT binary patch literal 1405 zcmb7^`#aQm6vsbbGZ>d)gpA8F>)s5xq%l%v#wFwul9gmyu`@2M2u&ehvlXiid2$() z!K890qcMOMs+Py{G^*W-EOASz6NX@$vL`+5SHvS(4b*TUcw73KQ?=eI$mQ zeONEGquyeaPg^Qg2Fr6?f#lMVyEE0FIlNFjn>B$`gxzt$P~RqbVIa|74nj;bs`I)^ zuOF1#k|H{s>7D5R?LFAG1*#>-*ImGT>Xq-hydzsUNV9-^&=NF=PeqS{!4%6iJ|cKh z8IxnTvMJYNC>9~Z^1p>hS$kbHp(?{RmhkmbsHb2P9=xEl7uRP+ANf|h+yr) z%b&WVf{2=wpw*(`8&ky^m5p)wAZcI7&P@vA+v?TDS?-V_pj*uR6;QCvsV8+l2Z67n z;$G-h-3`a`hE`Vv#@*g7_jWr>#A)1sJ3KPk2*KQfAoS;-5ahh)P?)k>lfqj3IcSF z;A07|7V7+z${6_LV;5CNm#2XqIS%EIg~wiWnA%I{9`-d$W=0@TS-E3K?yy1kz1ndi zR*2+4(z#s*=^?;HXbvdDGa1VpjpeFt7#QlSNO?sHy-#6lYQZM74{b3z?~NYw8bMNd zs`ut}EJ?|?*#$-U^DJd$UM2KvYZoLi1ehnXV}a!J-_py0a}6k7LXf^({9DY)ob;j? z=?Ub|n3eTuJIYf+^L)Ppd_WWnZ0sdXgyu11H$?IDru;(C7KqqDf$SFEQ7E6WU8o01 z2JD1P!xnjI;?(2rXyk1L-l%m@RL`^xcn26eHPad!B<-gNd(_*4W^FdbcQ+bBF<; zON^Y9J290bBpYKJIE}E|3+N}TFkyJ)o&(G&cd`d&B~>4wuC5jyqJ%b|X|#f;n3js8 zBPlBp$FTO6B6*Hx;U}SJ`PivF1xrB5J zwfpT;WLPs6LkqGsLH(=2YwC3r;2oRbmc5*^nKcV@oy?Tx5w zm`H@aniw(0=Rj%6!8f=zd)5L$*;I|Tix6@mj1bNZGSHjlXW-X;u2lw>cb>3Qo1713 z!sdQc1VSMd{o%q7X0MD|g|BKRg~`eOh(JGdObD{*7FOYje^eUcN=ruiTwiNhfMPeL z-lbvxD4F*l8Owfnw%@-$ymgS;XR@4YUft8Ga5cpY^USso^E&US6F&ERg_0GfUa;LO;2QxOJ(=*9+lBGRz( zp4GeRuDSZl0dZ)6Hvg<1la0&c(}~sxRHC;WEZb?#A4yGRZ1b#Ie*J;Mc!%?)sGJ(x z2Evkb1`1cp+{1Inv9{#r$+AFfJ3or%WFlLOAVtJrZuWzAaTo&h(>-E$^o>YilE&|2 zv`ot8Ck_B_`1xsTCFM&=suW`-o-1zxWM8nzj<^}yFl|-RbI#_$O!eieiq4Lub?Gyn z8;zqH`w@xyESRm(6z?6sJ-jH>2K@?akNpSo9i2|4X0-yz9|geO#oM{g@g(aXMRbVD literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/stick_r.png b/arcade/resources/assets/input_prompt/xbox/stick_r.png new file mode 100755 index 0000000000000000000000000000000000000000..852e140575cdb8919b6399c66c16909dd668b6bf GIT binary patch literal 1286 zcmb7EeKga182|1CW3+?{GlUW?Ekr^SHe4^e?Dj^6?m4?RG|f_^{Je&gx5Sp0MbYF$ zLLu!ZqaspW(WJ^RiqoclW>h?|shsJfG)t&ht6XU(Y9p7D(06GSUJ7==l10 z1*;hP7Z{YP&q@n_s0hvU2=D;lPCj-iQcb1pY5sKY)&El}lWN_dHEmust>pvGLyQeV4SFH4uD?i{mu&6x+f>=X2q1>U(?0bJz5sMu>}KD$}(R;4hB|4U-3x8&Dao#Rl8Dbtz!-ct9$S^~)G7 z8Z5Q_7?E`Hl_$0C*LTUkxhAVg=U47M#_zGxKYe*ZZtFsxKhq2Dq z0tq1tD7jS1eVa+-Z_;*F4fTy%jt;rOO*(~24oV^%u_$ldAleGuoT^i3OQY|GooeLB zs8L#c8wCjsFI=EVJ@)S|;vvgyG>x^%ig-P2^0ZlVCGOG?K=9o)#`Vej`}&{z7ub## zg+PmZ88fi#jG`Lx2@g-6ds;+5l^dF5e}Xkf5&A~J)ozjm^&nxFv{3c6jW2fNOGtcd z_DKjrOFc6`nW2_Q;g_9(lNcH(83whtf?H0I7h>XCdXYxtHShmk+4k2M+~P~X(S{#8 zAex$VV$cb?I4RYgCaN#NIX_SuTg1E|+_!~yZOhlU3Lq|=~o z*Qwwn8zeM0e82}ZmfTCcP(1a*>W~GI)Zay0Xcj%N)H`yhH}YDF}_>6G2C;ud_9wND~m^|C55|%rto~mef!*Yc=D($|0^Nv*u)%x#Tc8N zIN;HXhE;8yw^|I;u|Hgz`PcAHH=UCUNk_h;E2YTJ!7m@Z4gx6h* literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/stick_r_down.png b/arcade/resources/assets/input_prompt/xbox/stick_r_down.png new file mode 100755 index 0000000000000000000000000000000000000000..1930ed680138e4f769f3c7113f940c157dca49de GIT binary patch literal 1384 zcmb7^c|6p47{|Y34%dva>lw%M^&dfM-gd81?H8yu8wUWy5bJe)1Eu=EUDz9;7 z&>)j@L@Fs`NTb+g6_s+$*x70S+JE0000>0gZ5q` zLO&z{7xizv!VM7+;p9VP02=e91;I$sZtUsi;~@M$B{GT56&R_jqDeN^%ia5oO@6Y+ za{Ug2pm)#9uwB4Mm76U3)az_d_KD|yDxHj;RYzahnZ6SI^>2^%do`!!PNeO;9$5R^ z6vJgE`!uihP~YMXoA@r_qW)LlTTQD}4yT{Wfr081(4 zsaO>z=f+5c4Wahl*!oq)I?J_A?`RvaY~CnIwk_-paHt3uLzp>Q3nTkn%KQ-gAaOh^ z3BkNX+zEoNQ)(6llIqSIY5)`p;xa*7jF%xAEzYt{dz1_R+&#skc@y9yR)M4yq}1do zW|B_5lxdec^87K zR}wmpl=WNFXM1mk_(n$VwjR&3(A>P{b}dw4V8m02IW?hF<#b#gqOQ{AFnZXsmeI$0 zHoh9P^x=wZ&vD~?cz@v-I=5t;o8@>Xz%qQcGOXoj=(A~>1)-Qx`taN}Z%>R%yvp+1 z?JowV`)p&;&sUs|BmxO2w%nl9yER&WldHvC*x03sZFA5$GL-TzdPeaZ^<8 z-w$I+Z+<#(3Q<|7w^8LsA2S@g9>Ncrgz7#kKbeVat0zMd(m$aOD zw*2z;fyCd><9-el>u4grWhRyyQKGun{;(muR6Ez@W2n|v!UByMqR;o%7(Mu_<@SIX?ME} zGt~GxMXs?n4NC@EEqQ8f!zjl0zZqR_jlOJwGAZ;-ukM-{5Yy9%MsXyk&r%xhci@2K zyV1D)qzJjnXjF`fI%8zrq|_sCO1tWzW^p>#AMmt0lx&0Lm>o+ns0h)#x#=Z7mom5? zCTjuJL-p{EbiD52BsH2;>rkekcP@_Urz}{H@NQlF;%_|oJcq~^BE5>!DEf}0%r$z`#M`n>OX$Ehd2NL literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/stick_r_horizontal.png b/arcade/resources/assets/input_prompt/xbox/stick_r_horizontal.png new file mode 100755 index 0000000000000000000000000000000000000000..f7fa95fb6a575c20d4665c62fae109a9eab13f17 GIT binary patch literal 1324 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&v7|ftIx;Y9?C1WI$O_~e2l#}z z{wD8fByS;{rwDAxNg}3ZR+rJaSW-r^>+5# zZHu*d+LSd;r0@N|dhTZHTv1OI$Lephwmx|-=PjpLDcr=|A1_zHe22mO0562v&-p+> zYpS{PN{)B`8E3dn6;Dbr^}EkHYy> zgG%Su>;8Y{S$<_+azQk5BhRb}Rg7NWPcvP-bJFb%v&Ng*ce9tQXfx$v+xq>~rOKrH zTnAnS?lM?ce4xc$bTY5DO^Nlx7;dgdFJ`~}mb`ZU=T40R|KzSO`Z==Ko|gUl`R`p1 z-=8CiapuAWWY*v?V~{eRl-Uq|&*o2@TY zd+ZPA{J@!?vsO(c!Ph{$LA@t~?Z9hS*-s0OF@`ZMsGV_%fh9rlOwE=AMa~Al(h?pH z2A-e#&Wr~PFKuP@&i?1kz&B?Ccfv__C+UQFFKSJ6-!oXON<$Vli$uV_f zLB@t--AW90JEfv5?F$({*f||yycWgyVoKeFZUcrdmd)N9OEZi^Q*e!EG*Ey-VTajENg1?+R~aS7v+A?`71#cWEwTMiReH*rcD27hO?iIA_9; zR>NUulA*Ddrvk@(}< zRNs`X8EamgTXu2DE%O;A8Q)lsU6WyAFn@l_zHg7=k6&*;zo}xZTXbJJ;^Ma1EzybA zIbzBTaXWne-afPP_CKE~ z>^iD0*0PHhTAF(t$TCeSulu>(_P!Xym90~!-Ew!~{*l-rG9z-y%p0!n6)rGtG0ePq zt|P8gsETFHYb~#|kKD>3%QZ|FiIv5$WF6o6Sbk~H8vb>uD`rTrZ(6?Q>COi$7!1Q4 zKb~f}C2zW=)ZggS>D0sDQ=Mz4*4~ZgKT^>8Q@}qaZuaMIXFE^7p0`>mQ!0~djaB;o znI5;f>eHlsqW4;Ax|RM)dT>SX!rT}=D|v&-Ig{>78x^ZtpH`n}P*wIyY>K2%!}>YA z`3=$z3p$=@GyI#x^n~TW5%wATm=65!dJ=7`blit?5%bKQ~H X9K5yVheiUhykhWl^>bP0l+XkKqN-ux literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/stick_r_left.png b/arcade/resources/assets/input_prompt/xbox/stick_r_left.png new file mode 100755 index 0000000000000000000000000000000000000000..0d6b849e5e81c439f64aac9b0128d9249f1dd79b GIT binary patch literal 1317 zcmb7E`BPJ86#ZWE0$~fXYfuuTAX`8pB7++{iDCi)r&z57K?wqa2qBWHk3~%t5U7l7 zlA4GRL#$YFL1b~E;Kou=5CX9TQ2_%OQg)io^dIP*x%ZqibMBnq&d))#06lHIHUL16 zO7Uf=5&3tqX!TXf^5@h5M|PJ>RLKU|<=CblNLg3k%)&n2m>| zTi@Bgssz}#nyXtu*VpWqFSejRr9>YTpSD7xN>_4-LojoE2WQ_m9FnztkU*BzaJ%9W zEaud~(0;hhS3d}XNYCK1H~@Zce$JqIBA#1HkBOffNl!A!XGO) z{uKJyVbPd(bCieb;luM*TmxEj91qZ!PM9UU8VD`Be{_VbC#z_TUO=0))$J`WvIUY$z|aoNpBK#MF;Ie`Q2qAtBSeWI8DUo?p0**|Hd zZJ`gRH$E3GCY#G3_<}RRu>@$j3R2f2&{yuU`HvlxZ4kDX(gKz1zs5_m4x}}oL&Lqi zJ}w9^G3l%&pCwR@i9~-GlFbRs6NR>GsSyC$bjS|$v=^Gfj;k=`UnW;G!R6z52H-;e zHPYGS!GT!=OlN5;a~H?Z)fz(qbQqbA_t9WlCh2V$hdg1HjW=lG1cw6G z(Exie5WtHCzP1&*ExF9?B)tQ3n9v-Y(Z+o+Y%yH)Gy4(Bw0*(vgRi6x{XUxzzKb zTUXsjy)zGMr9?6fL>r>McYU5L4ta|2`;Kw(#&~}C_2fu5(Tjz%V=9bm*HNE&*F8wQ zgifv-$#E)D6$jr24Z@uutW8y#MakG?jQn~pM20Dvi1m=Y`Te4GbNmDGQen51y*AkD zW+2&Ln0U!w8C$v;*B@RpjR>-yvAJR+dNEOpA-2?y^O3qu%W0zOSO-;}`6?1sVS>ik zL^DzPX=vd-JvSRt-!KS=DGSt(Z`@`Z(n9W(;gx;?BCBVKNhe>XvRXBNY2}+6p4jlPgXb8bEAM_? zmFy0(pBzU8f_aIqpOOG!6?UKn(p}f|6O1gA3Pz^ZAUQ>bVVY@!#m@D8P%p@`DIDH0 zb)+4=gzBsWB~owxEZuyby?s$qoabM@d42`M=ncs!kmP0Rm#jp%{MR5%R3H+blHa_` TE;pxIT~vVTNAtbq&CK`*JA-Cm literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/stick_r_press.png b/arcade/resources/assets/input_prompt/xbox/stick_r_press.png new file mode 100755 index 0000000000000000000000000000000000000000..faed4c47ff53a9fbc5446df2645d3347bb4ab70d GIT binary patch literal 1364 zcmb7^`#;lr9LGO1%O%WA5p8q7G}kza5?z`K2 zpPbOYQijX>SALm94#il{J)QuxUs^AXLdbUqvOi^)?EjS9BtPH4&R>yFnwk3of>Hu6 z;oRxHwk*+M?sOdz5J{&v@=v#Yy*(*8JYkKX{OT4pXzyPNo2F#(+$^F0wrEK4ctA2k zT6^b(d1v|d_X7H%_pls*h3H6wO?CQ%`nm2L=A*<%7|u7CR0vMorYb4tN@lX4X5{Tj z5n`g*X**h}13wptXcT%$GRjh)&arxRiqU57@36(fh59|8jd7C+A^=c_sZ%C7gLFi( z1wR^8^$=3eN1Dqvl>gGD34%?=OewNm_2Dx{;5C*p=SI)4V7y61K*gPO9%k_Df-8sD z{>`p5@SHjv=p)1QS|f>{nMl8yzS-d`umcNA#@0L7PjEX@*Ltm%?Zq+Jdezl5_{skI zYiF@gTff7|A6QQ=Y;njoSScRA7RUaP zY6I2&;hu&?o3{f!d)$meZfp1DXF$syTM;TqzF%lh+6p;$sZ03es9Aykj<7?$zUO4#IQ|G3P5v9`qauYX0Xj~cuoL`v(2)&rK}PTg)Pg+Ot)x$p z&KuVs1=Bx?zTa(ZfM@$JWkUJb_rQSfq72m+F$eGt0shGLUNOx|1HY&DzMNO1o2#x|X6?l)w0x~J&0=zxXn%9Jy<(ZAg zOB(iL$u*SIS*KX&z$)ve`tKA)$*#EF^XqOn zJL)Gq6KAxn$BRFfRIwC;f2nRWYC8~nksABv>qn-GnZDZaSk;%EbV}bI<33#$_XW;u zB(1#Nd`$ECa83E#y*jkW^7!e>0Gq|N$132}_)0RZf6~{+(F$*6n!a$1;<6d|%!q+2 z_)6S_vY%2YZL6c literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/stick_r_right.png b/arcade/resources/assets/input_prompt/xbox/stick_r_right.png new file mode 100755 index 0000000000000000000000000000000000000000..0473f3ac6a473077ff8a4dd2cbc0379af72e8c08 GIT binary patch literal 1321 zcmb7^|3A}v6vyBDjLm0^;X#`(-Hj`^d|RZrbG0$#`+T{KL{sUiL@I7|uYFJ ztE;*_qBXJW`ck$`CMjPZ`I@1aZmua3+vTqNAKdeJobx*8@i?!?`RP1!SOH#moDmKH zfcM_T3|1-fB{k5hx+uN0q7t=(41WdyHTjxKj=E}gVEKi5tpA@-nN;frf{ah9j&LG4 zAS5w=CpYk`2lFp=#(%YoX#pC^Ga0H&=e(JW&>x5A%4YMX$XbXqbk?dvv2)I+Jl$DFy{nLpA*{-XkuF;NYOfh~F&4#K{$!`NKtOuirQPpfB->^_V270Nys({) zTuhttr9|sGJ2{S`*?z~4Mn~Ao-3;Rz(EP>sYn{^L(^PFjyR}vHFHg?~fm4tC8L5dS zBiZ|a(oFfDNbyOFL=`uLMPISZ15q!yC5D4>2neyDVPG2B5s9*GTYDQ$|!-~pjJNKpuQPT zE^e7DC`)(O*QfyKcNZlkL!SNS>0md7+q86ka_+Y?n5CTRL+SIHb!bvYsh%{N1SPE) zyBKI`iSHv#TiJsoVwfP~XV{0Y!v*kU8II6|fyjggEn z=ro>{u)UcQv6(=Ile9F^w&6KA;XK2JdPxKe9=8Y!4psDQ z@rWP8HD#(pavCfQ9Z-B*Oi8V{S@e8<8*(|Vo`>Q;NiDe5CR;NAF~_$KMai{$y+9)u zPU02bS`zi6DrkSYxXFXz))3#4#vuU?jEe3dL}NIx)}h8>c4W)qz+rsmb>=OX01FoB z?wdGC6ARV(#KJeMsw+EXz|h6DrEjZuUuko*zPE+Z*g!2u@XjwHhI1!kgG4>yOm3)F zYMEVoRG;TmXJ2B~9GWFTFS`Wy&zW%!eLoPpwJJ8bs$U-s5i5UaTo?w$$Vv2dU?hb4I6fn~*@D!3lWRi6Dz!|oZ#KYAbtzly_^=f6m~ z^lXtgsE~aU>tsJxXWch!9BdixezhbIcW}+Cc3f3Jq7#LcM@n-<=cetf|AUDB-Y-Ov YW2=fqv|cebLPx# literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/stick_r_up.png b/arcade/resources/assets/input_prompt/xbox/stick_r_up.png new file mode 100755 index 0000000000000000000000000000000000000000..98d7d1e4a948176cd0b4fe64a3fe91bfdf95a710 GIT binary patch literal 1370 zcmb7^|34FW9LGP~e3@m&%9pfV7S<^ZMJr)*H<=pwvV^W$oNp`A=}^R?YtEONiM}kY zICqrq5oXO)L~g!+SqO(%Ov$&@mQME{-0Sgpzn|~-HDr!}YqJGwdoP=0Nl@}%n8zRR6Pcl!E&wDz=iu3MG3yf$f{^K|n% zbL|kRJz;Zcy~W6h_b5KFWoo;uIX!lvIMQPVW_dFec}AKn9Zo9r%@=-D`&)so6&dFQp@NHz6U70T@?HtrB32^cEiy zDuP2Ii%T*_4v7ie5m#{ucjTa$kU8Sg0b^h_N9Dobz`O!3{cXMuF3p;cg*OAdH~ai1 z*1+Ag^m5mS1huk;Ft0j6T=hu-!$QTqUHov~0G?idwv&`pDS(UFN&H)b^PA&Z*DG~_ z|0{g|B0W&&8E}x7vWNYd#Osq=tU(d2FS=o}-u6>Qr7=*3ETEk@gn{>>(J#QaCvw-z zl3qF7gZocK+X+?e+*tuRHVI*g9d?Ly|8Rb1lM?P1ze?NHPM2ssv|Iw1;e>hh(xL`S zahL0qO*qnE|JS!s-ZXE_*=ZI^Pv{Au%m+NFcOVusOrkxuUG(Vy=p)=1GQrqi-go;! zgBhBoqS-v1jPV3l%*_D#)zq!j6|4K}sM=&mld*BA@Rvu=Z|WL~?+icZQ87*YN=6x& zX;~GfO?*IL1Oj~=%alKE{4j2aRXa!uyJ5jG9DL>Mgmp4YO~jMV{Uekl_4l? zBZJp7my4!_tBN#?c77?8LSt&}LyGA^&bbq$sok&1aDQv$Xn-wLh8lSQ3lH+7o2gdw0`je}?&Us&lzGPVrp)TZbx z!~PBd$3hJ>fsZ*Da;SO@#ykeuRn}_OkWN(D7RaAW#m!8}@s|r)>WowC%6Mo)GR(}c z1y#V+TG{JMhSfYcznLGXij_rjUcgKi<3j<}N8QDl9SttuPHnlX`YUI5iOqKme&Z3p ziH+coB)TZ2v5S@E)oLB>UV#8I^0qQ*7<=5lJTi3kN~z>hwr52q%+_>YRklGsfu8?; zr&|l9yx6~7w}N+|>N~vIQr56sz?hgRQtqwkit>w>QSNv(2l1ZI4P54W-fkemVkCyx z2&&FKbL`8M#_E^D{ckJjn0xk}Owh79i;fnuy=uSIfR& zU6eCO9o%suy9;(YlPOPuKf{zJg$eSdk$FN9Vaj<2INMg->?~w;qgT9l^f0wIe1J*| zFLi8R=Ct>&1ng_}*w8kFICv@}T+fc#y@XbcW|Z$wtd2F(qI*wgNa)KSg_T=ZOn6^n zmG=)9U{hqin6xg09uqm+5qVmFEYNrn`_mOv^H%KZ<*V=V1}EN$#UVqukQ$%Q9!Q%s z>x)V|eEml1pktF(!O76rGqmfxWBeF)i?L611g>8l1};AcA&RzJ?6lXRhMV2{k%;TQ z546!QHjS!@NeAYVTaSXLHX39S>nW_D8eCtXpHs7ik7nQ;6-0=Ugw^M6q?a>`&&q4t zBVE|@#G#7wzGmq7J3RtcU#SpTfE&0txgs(6Y1=t&o`HliMfuntMHC|=U_;ER*{1@GiiuwQm literal 0 HcmV?d00001 diff --git a/arcade/resources/assets/input_prompt/xbox/stick_r_vertical.png b/arcade/resources/assets/input_prompt/xbox/stick_r_vertical.png new file mode 100755 index 0000000000000000000000000000000000000000..5d2cfe94bb206e2eba7eccd410911a2d46f12c35 GIT binary patch literal 1465 zcmb7E`#;lr9R7S8GYm^*a@&-OoZP8{Vj`{P5;n4Abiv$CSxk(txkheXmc+3x%0f1* zD3^JmagH68N~9<$p_FQ*)y~eJaNe)i`+45a>-9XpJU_fo`FeY5sOhNz05piZ-Tf2{ z|F4g_epirE1i^~{fS_1i7N)Gww!WkQ5BdKYMMz;^BV$Dsn44tpi0z>JM-2f=BA-WR+n4=#q#TH+~qv3sT$h0MSr!?_h+sf$6%9Sq;*rbje zo_ydr>PEfElr3Ozuw38O{80F^2VB;8(NO78996Cx$wed*4b&P-RZ5cl-Co*2?!C+=!w=G6LeX#ed^R;hYz|2T`F`FYVp;nZ91Z86m ztutEWF~(Ya@6T_~EUqW*G{$BCen(KKxG(DGq{UW(rw{3zo7vwC5` z&IcjD$Qoa3ldz;}eRv!JUtxbu#r!oV1cZqrdj+0Z4Evrf6hO%eRz|FAX~ z#9||n`3UYS_Q(Llq|y1lymAQ7?cZhuIE~v{oY~N)_&-ayL=4bNj`KgMM)q-rOd_G{ zntU(%VX`VRn6M7o$aSeSgNJYD)}Kcv+LZ!z-zpl2u`Dl9QMS64t>nvH=4}J5{h z*5@l(p>fjyPNVIH!MXDdJUn7Z%>*!-QzfW`*nijv20AWB5D3P^oC9&J)7^7D_72;ygAjMF5gY zb!ufTFQX=xb7YRoS_K|v3`aKZ#fejBSj{7A6XS}`om2BEnk$(L2rlFoyWz?8YBWbGb8%hYmm=%Wdxb$f!eX9y<}blj zO2vWpF>SB*bkT3$tQwEMqwqM->4KfjNw0aq6afpXV;-`quaJ~APFoigsAyu;KS5qi z?zyp%y`mV=QyP6Q?`Gg_$Ye%*Z0@zqm5b8RGsU5w#VNPqUSyhx%&FahcRc1r#^pg~ z-&F!PoJ8f#{^%x4%%4_17fc%$TE7oh>1A(7nbsioXofWopNV_LFY<)@lNhF5@2{9G zLo-TwGEtQMl<>3Z8Bp5rkv^F`)77l{&w`|~kmQpU)T!k>Zn-PbVC3Nd^3d$h*icdj zxvx!QqWNVW<)u16S=BOaMc&-}U-@O}n!ss1JZ&o+t?h1uXhCDB5vc}}O-_KZoysQG zq^Sev>_s9T%@cnTN?Hzs;p3ZXu`d!;lmDqgSA8|6NpdH5|BZvw>#dc#5Ywbo+luB Lc)Q+Wc( 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 Date: Tue, 25 Mar 2025 23:33:14 +0100 Subject: [PATCH 18/34] Add method to access connected controllers --- arcade/experimental/controller_window.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arcade/experimental/controller_window.py b/arcade/experimental/controller_window.py index 4f1e01788..847a6ce2e 100644 --- a/arcade/experimental/controller_window.py +++ b/arcade/experimental/controller_window.py @@ -69,6 +69,10 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.cb = _WindowControllerBridge(self) + def get_controllers(self) -> list[Controller]: + """Return a list of connected controllers.""" + return self.cb.cm.get_controllers() + # Controller event mapping def on_stick_motion(self, controller: Controller, name, value): pass From 4b32537ca64718e1c7c89bfea85d09f43b96e34a Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Fri, 28 Mar 2025 11:06:25 +0100 Subject: [PATCH 19/34] fix linter --- arcade/examples/gui/exp_controller_support_grid.py | 5 +++-- arcade/experimental/controller_window.py | 1 - arcade/gui/ui_manager.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/arcade/examples/gui/exp_controller_support_grid.py b/arcade/examples/gui/exp_controller_support_grid.py index b7173967a..115ceb6c7 100644 --- a/arcade/examples/gui/exp_controller_support_grid.py +++ b/arcade/examples/gui/exp_controller_support_grid.py @@ -1,7 +1,8 @@ """ Example demonstrating a grid layout with focusable buttons in an Arcade GUI. -This example shows how to create a grid layout with buttons that can be navigated using a controller. +This example shows how to create a grid layout with buttons +that can be navigated using a controller. It includes a focus transition setup to allow smooth navigation between buttons in the grid. If Arcade and Python are properly installed, you can run this example with: @@ -31,7 +32,7 @@ class FocusableButton(Focusable, UIFlatButton): def setup_grid_focus_transition(grid: Dict[Tuple[int, int], UIWidget]): """Setup focus transition in grid. - Connect focus transition between `Focusable` in grid. + Connect focus transition between `Focusable` in grid. Args: grid: Dict[Tuple[int, int], Focusable]: grid of Focusable widgets. diff --git a/arcade/experimental/controller_window.py b/arcade/experimental/controller_window.py index 847a6ce2e..959206f42 100644 --- a/arcade/experimental/controller_window.py +++ b/arcade/experimental/controller_window.py @@ -18,7 +18,6 @@ class _WindowControllerBridge: that other systems should be aware, when not to act on events (like when the UI is active). """ - def __init__(self, window: arcade.Window): self.window = window diff --git a/arcade/gui/ui_manager.py b/arcade/gui/ui_manager.py index c1cda8f98..767bc50a5 100644 --- a/arcade/gui/ui_manager.py +++ b/arcade/gui/ui_manager.py @@ -42,7 +42,7 @@ ) from arcade.gui.surface import Surface from arcade.gui.widgets import UIWidget -from arcade.types import AnchorPoint, LBWH, Point2, Rect +from arcade.types import LBWH, AnchorPoint, Point2, Rect W = TypeVar("W", bound=UIWidget) From be976c40a296cd645617f85e1454e018af0d40cb Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Fri, 28 Mar 2025 11:07:58 +0100 Subject: [PATCH 20/34] fix linter --- arcade/examples/gui/exp_controller_support_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arcade/examples/gui/exp_controller_support_grid.py b/arcade/examples/gui/exp_controller_support_grid.py index 115ceb6c7..4fd8eaa95 100644 --- a/arcade/examples/gui/exp_controller_support_grid.py +++ b/arcade/examples/gui/exp_controller_support_grid.py @@ -32,7 +32,7 @@ class FocusableButton(Focusable, UIFlatButton): def setup_grid_focus_transition(grid: Dict[Tuple[int, int], UIWidget]): """Setup focus transition in grid. - Connect focus transition between `Focusable` in grid. + Connect focus transition between `Focusable` in grid. Args: grid: Dict[Tuple[int, int], Focusable]: grid of Focusable widgets. From 0dc37b483ea6f1c539d2936856f25b5fa317aee6 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Sat, 29 Mar 2025 21:37:33 +0100 Subject: [PATCH 21/34] add ControllerView and dispatch on_connect/disconnect events --- arcade/experimental/controller_window.py | 62 +++++++++++++++++++++++- arcade/gui/view.py | 18 ------- 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/arcade/experimental/controller_window.py b/arcade/experimental/controller_window.py index 959206f42..2e6d9e2cb 100644 --- a/arcade/experimental/controller_window.py +++ b/arcade/experimental/controller_window.py @@ -36,6 +36,8 @@ def on_connect(self, controller: Controller): except Exception as e: warnings.warn(f"Failed to open controller {controller}: {e}") + self.window.dispatch_event("on_connect", controller) + def on_disconnect(self, controller: Controller): controller.remove_handlers(self) @@ -44,7 +46,9 @@ def on_disconnect(self, controller: Controller): except Exception as e: warnings.warn(f"Failed to close controller {controller}: {e}") - # Controller event mapping + self.window.dispatch_event("on_disconnect", controller) + + # Controller input event mapping def on_stick_motion(self, controller: Controller, name, value): return self.window.dispatch_event("on_stick_motion", controller, name, value) @@ -62,7 +66,8 @@ def on_dpad_motion(self, controller: Controller, value): class ControllerWindow(arcade.Window): - """A window that listens to controller events and dispatches them via on_... hooks.""" + """A window that automatically opens and listens to controller events + and dispatches them via on_... hooks.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -73,24 +78,77 @@ def get_controllers(self) -> list[Controller]: return self.cb.cm.get_controllers() # Controller event mapping + def on_connect(self, controller: Controller): + """Called when a controller is connected. + The controller is already opened and ready to be used. + """ + pass + + def on_disconnect(self, controller: Controller): + """Called when a controller is disconnected.""" + pass + def on_stick_motion(self, controller: Controller, name, value): + """Called when a stick is moved.""" pass def on_trigger_motion(self, controller: Controller, name, value): + """Called when a trigger is moved.""" pass def on_button_press(self, controller: Controller, button): + """Called when a button is pressed.""" pass def on_button_release(self, controller: Controller, button): + """Called when a button is released.""" pass def on_dpad_motion(self, controller: Controller, value): + """Called when the dpad is moved.""" pass +ControllerWindow.register_event_type("on_connect") +ControllerWindow.register_event_type("on_disconnect") ControllerWindow.register_event_type("on_stick_motion") ControllerWindow.register_event_type("on_trigger_motion") ControllerWindow.register_event_type("on_button_press") ControllerWindow.register_event_type("on_button_release") ControllerWindow.register_event_type("on_dpad_motion") + + +class ControllerView(arcade.View): + """A view which predefines the controller event mapping methods. + + Can be used with a ControllerWindow to handle controller events.""" + + def on_connect(self, controller: Controller): + """Called when a controller is connected. + The controller is already opened and ready to be used. + """ + pass + + def on_disconnect(self, controller: Controller): + """Called when a controller is disconnected.""" + pass + + def on_stick_motion(self, controller: Controller, name, value): + """Called when a stick is moved.""" + pass + + def on_trigger_motion(self, controller: Controller, name, value): + """Called when a trigger is moved.""" + pass + + def on_button_press(self, controller: Controller, button): + """Called when a button is pressed.""" + pass + + def on_button_release(self, controller: Controller, button): + """Called when a button is released.""" + pass + + def on_dpad_motion(self, controller: Controller, value): + """Called when the dpad is moved.""" + pass diff --git a/arcade/gui/view.py b/arcade/gui/view.py index 6dc67205f..885f4587a 100644 --- a/arcade/gui/view.py +++ b/arcade/gui/view.py @@ -1,7 +1,5 @@ from typing import TypeVar -from pyglet.input import Controller - from arcade import View from arcade.gui.ui_manager import UIManager from arcade.gui.widgets import UIWidget @@ -61,19 +59,3 @@ def on_draw_before_ui(self): def on_draw_after_ui(self): """Use this method to draw custom elements after the UI elements are drawn.""" pass - - # Controller event mapping - def on_stick_motion(self, controller: Controller, name, value): - pass - - def on_trigger_motion(self, controller: Controller, name, value): - pass - - def on_button_press(self, controller: Controller, button): - pass - - def on_button_release(self, controller: Controller, button): - pass - - def on_dpad_motion(self, controller: Controller, value): - pass From 7f7fe955e7ebf234af0d27956d58f225337f6af5 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Tue, 8 Apr 2025 22:40:47 +0200 Subject: [PATCH 22/34] update pre-commit hooks --- .pre-commit-config.yaml | 5 ++++- arcade/gl/texture.py | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 953a84a4e..e0052c5d7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,8 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.10 + # Ruff version. + rev: v0.11.4 hooks: # Run the linter. - id: ruff @@ -21,7 +22,9 @@ repos: hooks: - id: mypy args: [ --explicit-package-bases ] + language: system - repo: https://github.com/RobertCraigie/pyright-python rev: v1.1.396 hooks: - id: pyright + language: system diff --git a/arcade/gl/texture.py b/arcade/gl/texture.py index 479fa23ce..fd88e6217 100644 --- a/arcade/gl/texture.py +++ b/arcade/gl/texture.py @@ -136,8 +136,8 @@ def __init__( self._component_size = 0 self._alignment = 1 self._target = target - self._samples = min(max(0, samples), self._ctx.info.MAX_SAMPLES) - self._depth = depth + self._samples: int = min(max(0, samples), self._ctx.info.MAX_SAMPLES) + self._depth: bool = depth self._immutable = immutable self._compare_func: str | None = None self._anisotropy = 1.0 From 66fef225d4774c45af76f57f14b7a9767fec8e79 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Tue, 8 Apr 2025 22:46:02 +0200 Subject: [PATCH 23/34] gui: replace experimental UIControllerBridge with experimental ControllerWindow --- arcade/examples/gui/exp_controller_support.py | 16 +- .../gui/exp_controller_support_grid.py | 10 +- arcade/gui/events.py | 75 ++++++++ arcade/gui/experimental/controller.py | 161 ------------------ arcade/gui/experimental/focus.py | 2 +- arcade/gui/ui_manager.py | 2 +- arcade/gui/widgets/dropdown.py | 2 +- 7 files changed, 88 insertions(+), 180 deletions(-) delete mode 100644 arcade/gui/experimental/controller.py diff --git a/arcade/examples/gui/exp_controller_support.py b/arcade/examples/gui/exp_controller_support.py index 07fc8a9af..f85b207fb 100644 --- a/arcade/examples/gui/exp_controller_support.py +++ b/arcade/examples/gui/exp_controller_support.py @@ -13,6 +13,7 @@ import arcade from arcade import Texture +from arcade.experimental.controller_window import ControllerWindow, ControllerView from arcade.gui import ( UIAnchorLayout, UIBoxLayout, @@ -26,8 +27,7 @@ UISlider, UIView, ) -from arcade.gui.experimental.controller import ( - UIControllerBridge, +from arcade.gui.events import ( UIControllerButtonEvent, UIControllerButtonPressEvent, UIControllerDpadEvent, @@ -138,9 +138,9 @@ def __init__(self): root = self.add(UIBoxLayout(space_between=10)) - root.add(UIFlatButton(text="Modal Button 1")) - root.add(UIFlatButton(text="Modal Button 2")) - root.add(UIFlatButton(text="Modal Button 3")) + root.add(UIFlatButton(text="Modal Button 1", width=200)) + root.add(UIFlatButton(text="Modal Button 2", width=200)) + root.add(UIFlatButton(text="Modal Button 3", width=200)) root.add(UIFlatButton(text="Close")).on_click = self.close self.detect_focusable_widgets() @@ -163,13 +163,11 @@ def close(self, event): self.parent.remove(self) -class MyView(UIView): +class MyView(ControllerView, UIView): def __init__(self): super().__init__() arcade.set_background_color(arcade.color.AMAZON) - self.controller_bridge = UIControllerBridge(self.ui) - base = self.add_widget(ControllerIndicator()) self.root = base.add(UIFocusGroup()) self.root.with_padding(left=10) @@ -195,6 +193,6 @@ def on_button_click(self, event: UIOnClickEvent): if __name__ == "__main__": - window = arcade.Window(title="Controller UI Example") + window = ControllerWindow(title="Controller UI Example") window.show_view(MyView()) arcade.run() diff --git a/arcade/examples/gui/exp_controller_support_grid.py b/arcade/examples/gui/exp_controller_support_grid.py index 4fd8eaa95..dfef1ca0d 100644 --- a/arcade/examples/gui/exp_controller_support_grid.py +++ b/arcade/examples/gui/exp_controller_support_grid.py @@ -13,15 +13,13 @@ import arcade from arcade.examples.gui.exp_controller_support import ControllerIndicator +from arcade.experimental.controller_window import ControllerView, ControllerWindow from arcade.gui import ( UIFlatButton, UIGridLayout, UIView, UIWidget, ) -from arcade.gui.experimental.controller import ( - UIControllerBridge, -) from arcade.gui.experimental.focus import Focusable, UIFocusGroup @@ -69,13 +67,11 @@ def setup_grid_focus_transition(grid: Dict[Tuple[int, int], UIWidget]): btn.neighbor_down = grid.get((c, 0)) -class MyView(UIView): +class MyView(ControllerView, UIView): def __init__(self): super().__init__() arcade.set_background_color(arcade.color.AMAZON) - self.controller_bridge = UIControllerBridge(self.ui) - self.root = self.add_widget(ControllerIndicator()) self.root = self.root.add(UIFocusGroup()) grid = self.root.add( @@ -95,6 +91,6 @@ def __init__(self): if __name__ == "__main__": - window = arcade.Window(title="Controller UI Example") + window = ControllerWindow(title="Controller UI Example") window.show_view(MyView()) arcade.run() diff --git a/arcade/gui/events.py b/arcade/gui/events.py index a150d1ce5..d5ca7df20 100644 --- a/arcade/gui/events.py +++ b/arcade/gui/events.py @@ -236,3 +236,78 @@ class UIOnActionEvent(UIEvent): """ action: Any + + +@dataclass +class UIControllerEvent(UIEvent): + """Base class for all UI controller events. + + Args: + source: The controller that triggered the event. + """ + + +@dataclass +class UIControllerStickEvent(UIControllerEvent): + """Triggered when a controller stick is moved. + + Args: + name: The name of the stick. + vector: The value of the stick. + """ + + name: str + vector: Vec2 + + +@dataclass +class UIControllerTriggerEvent(UIControllerEvent): + """Triggered when a controller trigger is moved. + + Args: + name: The name of the trigger. + value: The value of the trigger. + """ + + name: str + value: float + + +@dataclass +class UIControllerButtonEvent(UIControllerEvent): + """Triggered when a controller button used. + + Args: + button: The name of the button. + """ + + button: str + + +@dataclass +class UIControllerButtonPressEvent(UIControllerButtonEvent): + """Triggered when a controller button is pressed. + + Args: + button: The name of the button. + """ + + +@dataclass +class UIControllerButtonReleaseEvent(UIControllerButtonEvent): + """Triggered when a controller button is released. + + Args: + button: The name of the button. + """ + + +@dataclass +class UIControllerDpadEvent(UIControllerEvent): + """Triggered when a controller dpad is moved. + + Args: + vector: The value of the dpad. + """ + + vector: Vec2 diff --git a/arcade/gui/experimental/controller.py b/arcade/gui/experimental/controller.py deleted file mode 100644 index c81e778b4..000000000 --- a/arcade/gui/experimental/controller.py +++ /dev/null @@ -1,161 +0,0 @@ -import warnings -from dataclasses import dataclass -from typing import TYPE_CHECKING - -from pyglet.input import Controller -from pyglet.math import Vec2 - -from arcade import ControllerManager -from arcade.gui.events import UIEvent - -if TYPE_CHECKING: - from arcade.gui.ui_manager import UIManager - - -@dataclass -class UIControllerEvent(UIEvent): - """Base class for all UI controller events. - - Args: - source: The controller that triggered the event. - """ - - -@dataclass -class UIControllerStickEvent(UIControllerEvent): - """Triggered when a controller stick is moved. - - Args: - name: The name of the stick. - vector: The value of the stick. - """ - - name: str - vector: Vec2 - - -@dataclass -class UIControllerTriggerEvent(UIControllerEvent): - """Triggered when a controller trigger is moved. - - Args: - name: The name of the trigger. - value: The value of the trigger. - """ - - name: str - value: float - - -@dataclass -class UIControllerButtonEvent(UIControllerEvent): - """Triggered when a controller button used. - - Args: - button: The name of the button. - """ - - button: str - - -@dataclass -class UIControllerButtonPressEvent(UIControllerButtonEvent): - """Triggered when a controller button is pressed. - - Args: - button: The name of the button. - """ - - -@dataclass -class UIControllerButtonReleaseEvent(UIControllerButtonEvent): - """Triggered when a controller button is released. - - Args: - button: The name of the button. - """ - - -@dataclass -class UIControllerDpadEvent(UIControllerEvent): - """Triggered when a controller dpad is moved. - - Args: - vector: The value of the dpad. - """ - - vector: Vec2 - - -class _ControllerListener: - """Interface for listening to controller events""" - - def on_stick_motion(self, controller: Controller, name: str, value: Vec2): - pass - - def on_trigger_motion(self, controller: Controller, name: str, value: float): - pass - - def on_button_press(self, controller: Controller, button_name: str): - pass - - def on_button_release(self, controller: Controller, button_name: str): - pass - - def on_dpad_motion(self, controller: Controller, value: Vec2): - pass - - -class UIControllerBridge(_ControllerListener): - """Translates controller events to UIEvents and passes them to the UIManager. - - Controller are automatically connected and disconnected. - - Controller events are consumed by the UIControllerBridge, - if the UIEvent is consumed by the UIManager. - - This implicates, that the UIControllerBridge should be the first listener in the chain and - that other systems should be aware, when not to act on events (like when the UI is active). - """ - - def __init__(self, ui: "UIManager"): - self.ui = ui - self.cm = ControllerManager() - - self.cm.push_handlers(self) - # bind to existing controllers - for controller in self.cm.get_controllers(): - print("Controller connected", controller) - self.on_connect(controller) - - def on_connect(self, controller: Controller): - controller.push_handlers(self) - - try: - controller.open() - except Exception as e: - warnings.warn(f"Failed to open controller {controller}: {e}") - - def on_disconnect(self, controller: Controller): - controller.remove_handlers(self) - - try: - controller.close() - except Exception as e: - warnings.warn(f"Failed to close controller {controller}: {e}") - - # Controller event mapping - def on_stick_motion(self, controller: Controller, name, value): - return self.ui.dispatch_ui_event(UIControllerStickEvent(controller, name, value)) - - def on_trigger_motion(self, controller: Controller, name, value): - return self.ui.dispatch_ui_event(UIControllerTriggerEvent(controller, name, value)) - - def on_button_press(self, controller: Controller, button): - return self.ui.dispatch_ui_event(UIControllerButtonPressEvent(controller, button)) - - def on_button_release(self, controller: Controller, button): - return self.ui.dispatch_ui_event(UIControllerButtonReleaseEvent(controller, button)) - - def on_dpad_motion(self, controller: Controller, value): - return self.ui.dispatch_ui_event(UIControllerDpadEvent(controller, value)) diff --git a/arcade/gui/experimental/focus.py b/arcade/gui/experimental/focus.py index fe5ce5891..6da1a309c 100644 --- a/arcade/gui/experimental/focus.py +++ b/arcade/gui/experimental/focus.py @@ -13,7 +13,7 @@ UIMousePressEvent, UIMouseReleaseEvent, ) -from arcade.gui.experimental.controller import ( +from arcade.gui.events import ( UIControllerButtonPressEvent, UIControllerButtonReleaseEvent, UIControllerDpadEvent, diff --git a/arcade/gui/ui_manager.py b/arcade/gui/ui_manager.py index 463286b2e..352180681 100644 --- a/arcade/gui/ui_manager.py +++ b/arcade/gui/ui_manager.py @@ -31,7 +31,7 @@ UITextMotionEvent, UITextMotionSelectEvent, ) -from arcade.gui.experimental.controller import ( +from arcade.gui.events import ( UIControllerButtonPressEvent, UIControllerButtonReleaseEvent, UIControllerDpadEvent, diff --git a/arcade/gui/widgets/dropdown.py b/arcade/gui/widgets/dropdown.py index 14e264511..0c7693d61 100644 --- a/arcade/gui/widgets/dropdown.py +++ b/arcade/gui/widgets/dropdown.py @@ -7,7 +7,7 @@ from arcade import uicolor from arcade.gui import UIEvent, UIMousePressEvent from arcade.gui.events import UIOnChangeEvent, UIOnClickEvent -from arcade.gui.experimental.controller import UIControllerButtonPressEvent +from arcade.gui.events import UIControllerButtonPressEvent from arcade.gui.experimental.focus import UIFocusMixin from arcade.gui.ui_manager import UIManager from arcade.gui.widgets import UILayout, UIWidget From 22b130da277e6c2ecc34533397e4bc8480c2ecc4 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Fri, 11 Apr 2025 22:57:30 +0200 Subject: [PATCH 24/34] gui: fix example --- arcade/examples/gui/exp_inventory_demo.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/arcade/examples/gui/exp_inventory_demo.py b/arcade/examples/gui/exp_inventory_demo.py index 86faf0ff9..05f68c1c9 100644 --- a/arcade/examples/gui/exp_inventory_demo.py +++ b/arcade/examples/gui/exp_inventory_demo.py @@ -15,19 +15,20 @@ """ from functools import partial -# TODO: Drag and Drop +# TODO: Drag and Drop from typing import List import pyglet.font from pyglet.gl import GL_NEAREST import arcade -from arcade import Rect, open_window +from arcade import Rect from arcade.examples.gui.exp_controller_support_grid import ( ControllerIndicator, setup_grid_focus_transition, ) +from arcade.experimental.controller_window import ControllerWindow from arcade.gui import ( Property, Surface, @@ -41,7 +42,6 @@ UIWidget, bind, ) -from arcade.gui.experimental.controller import UIControllerBridge from arcade.gui.experimental.focus import Focusable, UIFocusGroup from arcade.resources import load_kenney_fonts @@ -359,8 +359,6 @@ class MyView(UIView): def __init__(self): super().__init__() - self.cb = UIControllerBridge(self.ui) - self.background_color = arcade.color.BLACK self.inventory = Inventory(30) @@ -397,7 +395,7 @@ def on_draw_before_ui(self): load_kenney_fonts() - open_window(window_title="Minimal example", width=1280, height=720, resizable=True).show_view( + ControllerWindow(title="Minimal example", width=1280, height=720, resizable=True).show_view( MyView() ) arcade.run() From 2e455be7a75170192a74549517cbdc9bd988780d Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Fri, 11 Apr 2025 23:13:51 +0200 Subject: [PATCH 25/34] gui: fix exp_inventory_demo.py --- arcade/examples/gui/exp_inventory_demo.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/arcade/examples/gui/exp_inventory_demo.py b/arcade/examples/gui/exp_inventory_demo.py index 05f68c1c9..7afd24e6b 100644 --- a/arcade/examples/gui/exp_inventory_demo.py +++ b/arcade/examples/gui/exp_inventory_demo.py @@ -14,8 +14,6 @@ python -m arcade.examples.gui.exp_inventory_demo """ -from functools import partial - # TODO: Drag and Drop from typing import List @@ -238,13 +236,13 @@ def __init__(self, **kwargs): equipment = Equipment() self.head_slot = self.add(EquipmentSlotUI(equipment, 0)) - self.head_slot.on_click = partial(self.dispatch_event, "on_slot_clicked", self.head_slot) + self.head_slot.on_click = lambda _: self.dispatch_event("on_slot_clicked", self.head_slot) self.chest_slot = self.add(EquipmentSlotUI(equipment, 1)) - self.chest_slot.on_click = partial(self.dispatch_event, "on_slot_clicked", self.chest_slot) + self.chest_slot.on_click = lambda _: self.dispatch_event("on_slot_clicked", self.chest_slot) self.legs_slot = self.add(EquipmentSlotUI(equipment, 2)) - self.legs_slot.on_click = partial(self.dispatch_event, "on_slot_clicked", self.legs_slot) + self.legs_slot.on_click = lambda _: self.dispatch_event("on_slot_clicked", self.legs_slot) EquipmentUI.register_event_type("on_slot_clicked") From 23bcfc2137adc487c89867f403c2fae02fc9029d Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Tue, 15 Apr 2025 20:40:47 +0200 Subject: [PATCH 26/34] gui: fix formating --- arcade/gui/experimental/focus.py | 8 +++----- arcade/gui/ui_manager.py | 12 +++++------- arcade/gui/widgets/dropdown.py | 3 +-- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/arcade/gui/experimental/focus.py b/arcade/gui/experimental/focus.py index 6da1a309c..c96d81551 100644 --- a/arcade/gui/experimental/focus.py +++ b/arcade/gui/experimental/focus.py @@ -7,17 +7,15 @@ import arcade from arcade import LBWH, MOUSE_BUTTON_LEFT from arcade.gui.events import ( + UIControllerButtonPressEvent, + UIControllerButtonReleaseEvent, + UIControllerDpadEvent, UIEvent, UIKeyPressEvent, UIKeyReleaseEvent, UIMousePressEvent, UIMouseReleaseEvent, ) -from arcade.gui.events import ( - UIControllerButtonPressEvent, - UIControllerButtonReleaseEvent, - UIControllerDpadEvent, -) from arcade.gui.property import ListProperty, Property, bind from arcade.gui.surface import Surface from arcade.gui.ui_manager import UIManager diff --git a/arcade/gui/ui_manager.py b/arcade/gui/ui_manager.py index 352180681..c881d4680 100644 --- a/arcade/gui/ui_manager.py +++ b/arcade/gui/ui_manager.py @@ -19,6 +19,11 @@ from arcade.experimental.controller_window import ControllerWindow from arcade.gui import UIEvent from arcade.gui.events import ( + UIControllerButtonPressEvent, + UIControllerButtonReleaseEvent, + UIControllerDpadEvent, + UIControllerStickEvent, + UIControllerTriggerEvent, UIKeyPressEvent, UIKeyReleaseEvent, UIMouseDragEvent, @@ -31,13 +36,6 @@ UITextMotionEvent, UITextMotionSelectEvent, ) -from arcade.gui.events import ( - UIControllerButtonPressEvent, - UIControllerButtonReleaseEvent, - UIControllerDpadEvent, - UIControllerStickEvent, - UIControllerTriggerEvent, -) from arcade.gui.surface import Surface from arcade.gui.widgets import UIWidget from arcade.types import LBWH, AnchorPoint, Point2, Rect diff --git a/arcade/gui/widgets/dropdown.py b/arcade/gui/widgets/dropdown.py index 0c7693d61..a647423c3 100644 --- a/arcade/gui/widgets/dropdown.py +++ b/arcade/gui/widgets/dropdown.py @@ -6,8 +6,7 @@ import arcade from arcade import uicolor from arcade.gui import UIEvent, UIMousePressEvent -from arcade.gui.events import UIOnChangeEvent, UIOnClickEvent -from arcade.gui.events import UIControllerButtonPressEvent +from arcade.gui.events import UIControllerButtonPressEvent, UIOnChangeEvent, UIOnClickEvent from arcade.gui.experimental.focus import UIFocusMixin from arcade.gui.ui_manager import UIManager from arcade.gui.widgets import UILayout, UIWidget From 4a3621a156188fd50d1e4eb7b6698a7a5175e7bc Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Tue, 15 Apr 2025 20:51:02 +0200 Subject: [PATCH 27/34] fix line length test raises on >=100 instead >100 --- tests/integration/examples/test_line_lengths.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/examples/test_line_lengths.py b/tests/integration/examples/test_line_lengths.py index 50b65d42a..5682c6153 100644 --- a/tests/integration/examples/test_line_lengths.py +++ b/tests/integration/examples/test_line_lengths.py @@ -1,6 +1,6 @@ """ Examples should never exceed a certain line length to ensure readability -in the documentation. The source code gets clipped after 90 ish characters. +in the documentation. The source code gets clipped after 100 ish characters. Adapted from util/check_example_line_length.py """ @@ -29,7 +29,7 @@ def is_ignored(path: Path): def test_line_lengths(): paths = EXAMPLE_ROOT.glob("**/*.py") - regex = re.compile("^.{100}.*$") + regex = re.compile("^.{100}.+$") grand_total = 0 file_count = 0 @@ -42,7 +42,7 @@ def test_line_lengths(): with open(path, encoding="utf8") as f: for line in f: line_no += 1 - result = regex.search(line.strip("\r")) + result = regex.search(line.strip("\r").strip("\n")) if result: print(f" {path.relative_to(EXAMPLE_ROOT)}:{line_no}: " + line.strip()) grand_total += 1 From 5e7b63a143d7ba99b0448ff23e703dea0965df95 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Sat, 19 Apr 2025 22:07:28 +0200 Subject: [PATCH 28/34] gui: controller support - add connect/disconnect callbacks - update inventory example - UIFlatButton and UITexturedButton show hover style - Add FocusMode to UIWidget, to control focus behaviour --- ...ry_demo.py => exp_controller_inventory.py} | 62 +++-- arcade/gui/events.py | 18 ++ arcade/gui/experimental/focus.py | 229 ++++++++---------- arcade/gui/ui_manager.py | 14 ++ arcade/gui/widgets/__init__.py | 52 +++- arcade/gui/widgets/buttons.py | 4 +- arcade/gui/widgets/dropdown.py | 12 +- 7 files changed, 235 insertions(+), 156 deletions(-) rename arcade/examples/gui/{exp_inventory_demo.py => exp_controller_inventory.py} (86%) diff --git a/arcade/examples/gui/exp_inventory_demo.py b/arcade/examples/gui/exp_controller_inventory.py similarity index 86% rename from arcade/examples/gui/exp_inventory_demo.py rename to arcade/examples/gui/exp_controller_inventory.py index 7afd24e6b..6535e2349 100644 --- a/arcade/examples/gui/exp_inventory_demo.py +++ b/arcade/examples/gui/exp_controller_inventory.py @@ -11,14 +11,16 @@ - Controller support If Arcade and Python are properly installed, you can run this example with: -python -m arcade.examples.gui.exp_inventory_demo +python -m arcade.examples.gui.exp_controller_inventory """ # TODO: Drag and Drop -from typing import List +from typing import List, Optional import pyglet.font +from pyglet.event import EVENT_HANDLED from pyglet.gl import GL_NEAREST +from pyglet.input import Controller import arcade from arcade import Rect @@ -26,7 +28,7 @@ ControllerIndicator, setup_grid_focus_transition, ) -from arcade.experimental.controller_window import ControllerWindow +from arcade.experimental.controller_window import ControllerWindow, ControllerView from arcade.gui import ( Property, Surface, @@ -39,7 +41,9 @@ UIView, UIWidget, bind, + UIEvent, ) +from arcade.gui.events import UIControllerButtonPressEvent from arcade.gui.experimental.focus import Focusable, UIFocusGroup from arcade.resources import load_kenney_fonts @@ -297,7 +301,7 @@ def __init__(self, inventory: Inventory, **kwargs): super().__init__(size_hint=(0.8, 0.8), **kwargs) self.with_padding(all=10) self.with_background(color=arcade.uicolor.GREEN_GREEN_SEA) - self._debug = True + self._debug = False self.add( UILabel(text="Inventory", font_size=20, font_name="Kenney Blocks", bold=True), @@ -336,24 +340,32 @@ def __init__(self, inventory: Inventory, **kwargs): inv_slot.neighbor_right = eq_slot eq_slot.neighbor_left = inv_slot - # focusable widgets - self.detect_focusable_widgets() - - # close button, not focusable (controller use B to close) - close_button = self.add( + # close button not part of the normal focus rotation, but can be focused with "b" + self.close_button = self.add( # todo: find out why X is not in center UIFlatButton(text="X", width=40, height=40), anchor_x="right", anchor_y="top", ) - close_button.on_click = lambda _: self.close() # type: ignore + self.close_button.on_click = lambda _: self.close() # type: ignore + + # init controller support + self.detect_focusable_widgets() + + def on_event(self, event: UIEvent) -> Optional[bool]: + if isinstance(event, UIControllerButtonPressEvent): + if event.button == "b": + self.set_focus(self.close_button) + return EVENT_HANDLED + + return super().on_event(event) def close(self): + self.visible = False self.trigger_full_render() - self.parent.remove(self) -class MyView(UIView): +class MyView(UIView, ControllerView): def __init__(self): super().__init__() @@ -368,26 +380,40 @@ def __init__(self): self.root = self.add_widget(UIAnchorLayout()) self.add_widget(ControllerIndicator()) - self.show_inventory() + text = self.root.add( + UILabel( + text="Open Inventory with 'Select' button on a controller or 'I' key", font_size=24 + ) + ) + text.fit_content() + text.center_on_screen() + + self._inventory_modal = self.root.add(InventoryModal(self.inventory)) - def show_inventory(self): - self.root.add(InventoryModal(self.inventory)) + def toggle_inventory(self): + self._inventory_modal.visible = not self._inventory_modal.visible def on_key_press(self, symbol: int, modifiers: int) -> bool | None: if symbol == arcade.key.I: - print("Show inventory") - for i, item in enumerate(self.inventory): - print(i, item.symbol if item else "-") + self.toggle_inventory() return True return super().on_key_press(symbol, modifiers) + def on_button_press(self, controller: Controller, button): + if button == "back": + self.toggle_inventory() + return True + + return super().on_button_press(controller, button) + def on_draw_before_ui(self): pass if __name__ == "__main__": # pixelate the font + pyglet.options.text_antialiasing = False pyglet.font.base.Font.texture_min_filter = GL_NEAREST pyglet.font.base.Font.texture_mag_filter = GL_NEAREST diff --git a/arcade/gui/events.py b/arcade/gui/events.py index d5ca7df20..27476d439 100644 --- a/arcade/gui/events.py +++ b/arcade/gui/events.py @@ -247,6 +247,24 @@ class UIControllerEvent(UIEvent): """ +@dataclass +class UIControllerConnectEvent(UIControllerEvent): + """Triggered when a controller is connected. + + Args: + source: The controller that triggered the event. + """ + + +@dataclass +class UIControllerDisconnectEvent(UIControllerEvent): + """Triggered when a controller is disconnected. + + Args: + source: The controller that triggered the event. + """ + + @dataclass class UIControllerStickEvent(UIControllerEvent): """Triggered when a controller stick is moved. diff --git a/arcade/gui/experimental/focus.py b/arcade/gui/experimental/focus.py index c96d81551..2cff7bdc6 100644 --- a/arcade/gui/experimental/focus.py +++ b/arcade/gui/experimental/focus.py @@ -1,15 +1,17 @@ import warnings +from types import EllipsisType from typing import Optional from pyglet.event import EVENT_HANDLED, EVENT_UNHANDLED from pyglet.math import Vec2 import arcade -from arcade import LBWH, MOUSE_BUTTON_LEFT +from arcade import MOUSE_BUTTON_LEFT from arcade.gui.events import ( UIControllerButtonPressEvent, UIControllerButtonReleaseEvent, UIControllerDpadEvent, + UIControllerEvent, UIEvent, UIKeyPressEvent, UIKeyReleaseEvent, @@ -18,15 +20,14 @@ ) from arcade.gui.property import ListProperty, Property, bind from arcade.gui.surface import Surface -from arcade.gui.ui_manager import UIManager -from arcade.gui.widgets import UIInteractiveWidget, UIWidget +from arcade.gui.widgets import FocusMode, UIInteractiveWidget, UIWidget from arcade.gui.widgets.layout import UIAnchorLayout from arcade.gui.widgets.slider import UIBaseSlider class Focusable(UIWidget): """ - A widget that can be focused and provides additional information about focus behavior. + A widget that provides additional information about focus neighbors. Attributes: @@ -34,80 +35,36 @@ class Focusable(UIWidget): neighbor_right: The widget right of this widget. neighbor_down: The widget below this widget. neighbor_left: The widget left of this widget. - """ - # todo set focused when focused - focused = Property(False) + focus_mode = FocusMode.ALL neighbor_up: UIWidget | None = None neighbor_right: UIWidget | None = None neighbor_down: UIWidget | None = None neighbor_left: UIWidget | None = None - @property - def ui(self) -> UIManager | None: - """The UIManager this widget is attached to.""" - w: UIWidget | None = self - while w and w.parent: - parent = w.parent - if isinstance(parent, UIManager): - return parent - - w = parent - return None - - def _render_focus(self, surface: Surface): - # this will be properly integrated into widget - self.prepare_render(surface) - arcade.draw_rect_outline( - rect=LBWH(0, 0, self.content_width, self.content_height), - color=arcade.color.WHITE, - border_width=4, - ) - - def _do_render(self, surface: Surface, force=False) -> bool: - rendered = False - - should_render = force or self._requires_render - if should_render and self.visible: - rendered = True - self.do_render_base(surface) - self.do_render(surface) - - if self.focused: - self._render_focus(surface) - - self._requires_render = False - - # only render children if self is visible - if self.visible: - for child in self.children: - rendered |= child._do_render(surface, should_render) - - return rendered - class UIFocusMixin(UIWidget): """A group of widgets that can be focused. UIFocusGroup maintains two lists of widgets: - The list of focusable widgets. - - The list of widgets in. + - The list of widgets within (normal widget children). Use `detect_focusable_widgets()` to automatically detect focusable widgets - or add_widget to add them manually. + or explicitly use `add_widget()`. - The Group can be navigated with the keyboard or controller. + The Group can be navigated with the keyboard (TAB/ SHIFT + TAB) or controller (DPad). - DPAD: Navigate between focusable widgets. (up, down, left, right) - TAB: Navigate between focusable widgets. - - A Button or SPACE: Interact with the focused widget. + - 'A' Button or SPACE: Interact with the focused widget. """ + _focused_widget = Property[UIWidget | None](None) _focusable_widgets = ListProperty[UIWidget]() - _focused = Property(0) _interacting: UIWidget | None = None _debug = Property(False) @@ -116,13 +73,21 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) bind(self, "_debug", self.trigger_full_render) - bind(self, "_focused", self.trigger_full_render) + bind(self, "_focused_widget", self.trigger_full_render) bind(self, "_focusable_widgets", self.trigger_full_render) def on_event(self, event: UIEvent) -> Optional[bool]: + # pass events to children first, including controller events + # so they can handle them if super().on_event(event): return EVENT_HANDLED + if isinstance(event, UIControllerEvent): + # if no focused widget, set the first focusable widget + if self.focused_widget is None and self._focusable_widgets: + self.set_focus() + return EVENT_HANDLED + if isinstance(event, UIKeyPressEvent): if event.symbol == arcade.key.TAB: if event.modifiers & arcade.key.MOD_SHIFT: @@ -141,7 +106,7 @@ def on_event(self, event: UIEvent) -> Optional[bool]: self.end_interaction() return EVENT_HANDLED - if isinstance(event, UIControllerDpadEvent): + elif isinstance(event, UIControllerDpadEvent): if self._interacting: # TODO this should be handled in the slider! # pass dpad events to the interacting widget @@ -184,44 +149,15 @@ def on_event(self, event: UIEvent) -> Optional[bool]: return EVENT_UNHANDLED - def _ensure_focused_property(self): - # TODO this is a hack, to set the focused property on the focused widget - # this should be properly handled in a property or so - - focused = self._get_focused_widget() - - for widget in self._focusable_widgets: - if isinstance(widget, Focusable): - if widget == focused: - widget.focused = True - else: - widget.focused = False - - def _get_focused_widget(self) -> UIWidget | None: - if len(self._focusable_widgets) == 0: - return None - - if len(self._focusable_widgets) <= self._focused < 0: - warnings.warn("Focused widget is out of range") - self._focused = 0 - - return self._focusable_widgets[self._focused] - - def add_widget(self, widget): - self._focusable_widgets.append(widget) - @classmethod def _walk_widgets(cls, root: UIWidget): for child in reversed(root.children): yield child yield from cls._walk_widgets(child) - def detect_focusable_widgets(self, root: UIWidget | None = None): + def detect_focusable_widgets(self): """Automatically detect focusable widgets.""" - if root is None: - root = self - - widgets = self._walk_widgets(root) + widgets = self._walk_widgets(self) focusable_widgets = [] for widget in reversed(list(widgets)): @@ -230,58 +166,119 @@ def detect_focusable_widgets(self, root: UIWidget | None = None): self._focusable_widgets = focusable_widgets + @property + def focused_widget(self) -> UIWidget | None: + """Return the currently focused widget. + If no widget is focused, return None.""" + return self._focused_widget + + def set_focus(self, widget: UIWidget | None | EllipsisType = ...): + """Set the focus to a specific widget. + + Set the focus to a specific widget. The widget must be in the list of + focusable widgets. If the widget is not in the list, a ValueError is raised. + + Setting the focus to None will remove the focus from the current widget. + If `...` is passed (default), the focus will be set to the first + focusable widget in the list. + + Args: + widget: The widget to focus. + """ + # de-focus the current widget + if widget is None: + if self.focused_widget is not None: + self.focused_widget.focused = False + self._focused_widget = None + return + + # resolve ... + if widget is Ellipsis: + if self._focusable_widgets: + widget = self._focusable_widgets[0] + else: + raise ValueError( + "No focusable widgets in the group, " + "use `detect_focusable_widgets()` to detect them." + ) + + # handle new focus + if widget not in self._focusable_widgets: + raise ValueError("Widget is not focusable or not in the group.") + + if self.focused_widget is not None: + self.focused_widget.focused = False + widget.focused = True + self._focused_widget = widget + def focus_up(self): - widget = self._get_focused_widget() + widget = self.focused_widget if isinstance(widget, Focusable): if widget.neighbor_up: - _index = self._focusable_widgets.index(widget.neighbor_up) - self._focused = _index + self.set_focus(widget.neighbor_up) return self.focus_previous() def focus_down(self): - widget = self._get_focused_widget() + widget = self.focused_widget if isinstance(widget, Focusable): if widget.neighbor_down: - _index = self._focusable_widgets.index(widget.neighbor_down) - self._focused = _index + self.set_focus(widget.neighbor_down) return self.focus_next() def focus_left(self): - widget = self._get_focused_widget() + widget = self.focused_widget if isinstance(widget, Focusable): if widget.neighbor_left: - _index = self._focusable_widgets.index(widget.neighbor_left) - self._focused = _index + self.set_focus(widget.neighbor_left) return self.focus_previous() def focus_right(self): - widget = self._get_focused_widget() + widget = self.focused_widget if isinstance(widget, Focusable): if widget.neighbor_right: - _index = self._focusable_widgets.index(widget.neighbor_right) - self._focused = _index + self.set_focus(widget.neighbor_right) return self.focus_next() def focus_next(self): - self._focused += 1 - if self._focused >= len(self._focusable_widgets): - self._focused = 0 + """Focus the next widget in the list of focusable widgets of this group""" + if self.focused_widget is None: + warnings.warn("No focused widget. Do not change focus.") + return + + if self.focused_widget not in self._focusable_widgets: + warnings.warn("Focused widget not in focusable widgets list. Do not change focus.") + return + + focused_index = self._focusable_widgets.index(self.focused_widget) + 1 + focused_index %= len(self._focusable_widgets) # wrap around + self.set_focus(self._focusable_widgets[focused_index]) def focus_previous(self): - self._focused -= 1 - if self._focused < 0: - self._focused = len(self._focusable_widgets) - 1 + """Focus the previous widget in the list of focusable widgets of this group""" + if self.focused_widget is None: + warnings.warn("No focused widget. Do not change focus.") + return + + if self.focused_widget not in self._focusable_widgets: + warnings.warn("Focused widget not in focusable widgets list. Do not change focus.") + return + + focused_index = self._focusable_widgets.index(self.focused_widget) - 1 + # automatically wrap around via index -1 + self.set_focus(self._focusable_widgets[focused_index]) def start_interaction(self): - widget = self._get_focused_widget() + # TODO this should be handled in the widget + + widget = self.focused_widget if isinstance(widget, UIInteractiveWidget): widget.dispatch_ui_event( @@ -298,7 +295,7 @@ def start_interaction(self): print("Cannot interact widget") def end_interaction(self): - widget = self._get_focused_widget() + widget = self.focused_widget if isinstance(widget, UIInteractiveWidget): if isinstance(self._interacting, UIBaseSlider): @@ -321,10 +318,6 @@ def end_interaction(self): ) def _do_render(self, surface: Surface, force=False) -> bool: - # TODO this is a hack, to set the focused property on the focused widget - self._ensure_focused_property() - - # TODO: add a post child render hook to UIWidget rendered = super()._do_render(surface, force) if rendered: @@ -335,20 +328,10 @@ def _do_render(self, surface: Surface, force=False) -> bool: def do_post_render(self, surface: Surface): surface.limit(None) - widget = self._get_focused_widget() + widget = self.focused_widget if not widget: return - if isinstance(widget, Focusable): - # Focusable widgets care about focus themselves - pass - else: - arcade.draw_rect_outline( - rect=widget.rect, - color=arcade.color.WHITE, - border_width=2, - ) - if self._debug: # debugging if isinstance(widget, Focusable): @@ -383,7 +366,7 @@ def _draw_indicator(self, start: Vec2, end: Vec2, color=arcade.color.WHITE): @staticmethod def is_focusable(widget): - return isinstance(widget, (Focusable, UIInteractiveWidget)) + return widget.focus_mode is not FocusMode.NONE class UIFocusGroup(UIFocusMixin, UIAnchorLayout): diff --git a/arcade/gui/ui_manager.py b/arcade/gui/ui_manager.py index c881d4680..1fb813839 100644 --- a/arcade/gui/ui_manager.py +++ b/arcade/gui/ui_manager.py @@ -35,6 +35,8 @@ UITextInputEvent, UITextMotionEvent, UITextMotionSelectEvent, + UIControllerConnectEvent, + UIControllerDisconnectEvent, ) from arcade.gui.surface import Surface from arcade.gui.widgets import UIWidget @@ -288,6 +290,8 @@ def enable(self) -> None: if isinstance(self.window, ControllerWindow): controller_handlers = { + self.on_connect, + self.on_disconnect, self.on_stick_motion, self.on_trigger_motion, self.on_button_press, @@ -324,6 +328,8 @@ def disable(self) -> None: if isinstance(self.window, ControllerWindow): controller_handlers = { + self.on_connect, + self.on_disconnect, self.on_stick_motion, self.on_trigger_motion, self.on_button_press, @@ -483,6 +489,14 @@ def on_resize(self, width, height): self.trigger_render() + def on_connect(self, controller: Controller): + """Called when a controller is connected.""" + self.dispatch_ui_event(UIControllerConnectEvent(controller)) + + def on_disconnect(self, controller: Controller): + """Called when a controller is disconnected.""" + self.dispatch_ui_event(UIControllerDisconnectEvent(controller)) + def on_stick_motion(self, controller: Controller, name, value): return self.dispatch_ui_event(UIControllerStickEvent(controller, name, value)) diff --git a/arcade/gui/widgets/__init__.py b/arcade/gui/widgets/__init__.py index d03b8382e..e2b0e6956 100644 --- a/arcade/gui/widgets/__init__.py +++ b/arcade/gui/widgets/__init__.py @@ -1,5 +1,8 @@ +from __future__ import annotations + from abc import ABC -from typing import Dict, Iterable, List, NamedTuple, Optional, TYPE_CHECKING, Tuple, TypeVar, Union +from enum import IntEnum +from typing import TYPE_CHECKING, Dict, Iterable, List, NamedTuple, Optional, Tuple, TypeVar, Union from pyglet.event import EVENT_HANDLED, EVENT_UNHANDLED, EventDispatcher from pyglet.math import Vec2 @@ -25,11 +28,22 @@ if TYPE_CHECKING: from arcade.gui.ui_manager import UIManager -__all__ = ["Surface", "UIDummy"] - W = TypeVar("W", bound="UIWidget") +class FocusMode(IntEnum): + """Defines the focus mode of a widget. + + 0: Not focusable + 1: Focusable + + We might support different focus modes in the future, but for now on/off is enough. + """ + + NONE = 0 + ALL = 2 + + class _ChildEntry(NamedTuple): child: "UIWidget" data: Dict @@ -57,6 +71,8 @@ class UIWidget(EventDispatcher, ABC): rect = Property(LBWH(0, 0, 1, 1)) visible = Property(True) + focused = Property(False) + focus_mode: FocusMode = FocusMode.NONE size_hint = Property[Optional[Tuple[Optional[float], Optional[float]]]](None) size_hint_min = Property[Optional[Tuple[Optional[float], Optional[float]]]](None) @@ -107,6 +123,7 @@ def __init__( self.add(child) bind(self, "rect", self.trigger_full_render) + bind(self, "focused", self.trigger_full_render) bind( self, "visible", self.trigger_full_render ) # TODO maybe trigger_parent_render would be enough @@ -242,6 +259,8 @@ def _do_render(self, surface: Surface, force=False) -> bool: rendered = True self.do_render_base(surface) self.do_render(surface) + if self.focused: + self.do_render_focus(surface) self._requires_render = False # only render children if self is visible @@ -292,6 +311,15 @@ def do_render(self, surface: Surface): """ pass + def do_render_focus(self, surface: Surface): + """Render the widgets focus representation overlay`""" + self.prepare_render(surface) + arcade.draw_rect_outline( + rect=LBWH(0, 0, self.content_width, self.content_height), + color=arcade.color.WHITE, + border_width=4, + ) + def dispatch_ui_event(self, event: UIEvent): """Dispatch a :class:`UIEvent` using pyglet event dispatch mechanism""" return self.dispatch_event("on_event", event) @@ -314,6 +342,19 @@ def scale(self, factor: AsFloat, anchor: Vec2 = AnchorPoint.CENTER): """ self.rect = self.rect.scale(new_scale=factor, anchor=anchor) + def get_ui_manager(self) -> UIManager | None: + """The UIManager this widget is attached to. During creation, this will be None.""" + from arcade.gui.ui_manager import UIManager + + w: UIWidget | None = self + while w and w.parent: + parent = w.parent + if isinstance(parent, UIManager): + return parent + + w = parent + return None + @property def left(self) -> float: """Left coordinate of the widget""" @@ -550,6 +591,8 @@ class UIInteractiveWidget(UIWidget): the interaction (default: left mouse button) """ + focus_mode = FocusMode.ALL + # States hovered = Property(False) """True if the mouse is over the widget""" @@ -867,3 +910,6 @@ def color(self): @color.setter def color(self, value): self.with_background(color=value) + + +__all__ = ["Surface", "UIDummy", "FocusMode", "UIInteractiveWidget", "UIWidget"] diff --git a/arcade/gui/widgets/buttons.py b/arcade/gui/widgets/buttons.py index 22ffa4db2..60136e539 100644 --- a/arcade/gui/widgets/buttons.py +++ b/arcade/gui/widgets/buttons.py @@ -150,7 +150,7 @@ def get_current_state(self) -> str: return "disabled" elif self.pressed: return "press" - elif self.hovered: + elif self.hovered or self.focused: return "hover" else: return "normal" @@ -346,7 +346,7 @@ def get_current_state(self) -> str: return "disabled" elif self.pressed: return "press" - elif self.hovered: + elif self.hovered or self.focused: return "hover" else: return "normal" diff --git a/arcade/gui/widgets/dropdown.py b/arcade/gui/widgets/dropdown.py index a647423c3..27c233291 100644 --- a/arcade/gui/widgets/dropdown.py +++ b/arcade/gui/widgets/dropdown.py @@ -27,6 +27,7 @@ def show(self, manager: UIManager): def hide(self): """Hide the overlay.""" + self.set_focus(None) if self.parent: self.parent.remove(self) @@ -196,17 +197,8 @@ def _update_options(self): self._overlay.detect_focusable_widgets() - def _find_ui_manager(self): - # search tree for UIManager - parent = self.parent - while isinstance(parent, UIWidget): - # - parent = parent.parent - - return parent if isinstance(parent, UIManager) else None - def _show_overlay(self): - manager = self._find_ui_manager() + manager = self.get_ui_manager() if manager is None: raise Exception("UIDropdown could not find UIManager in its parents.") From 86c8c984b6850881a3b9947dcabcab76092e243b Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Sat, 19 Apr 2025 22:46:13 +0200 Subject: [PATCH 29/34] gui: controller support - add tests - add type hints --- .pre-commit-config.yaml | 2 + arcade/gui/ui_manager.py | 11 ++--- tests/unit/gui/test_focus.py | 80 ++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 tests/unit/gui/test_focus.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e0052c5d7..dd6633f90 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,8 +23,10 @@ repos: - id: mypy args: [ --explicit-package-bases ] language: system + exclude: ^tests/ - repo: https://github.com/RobertCraigie/pyright-python rev: v1.1.396 hooks: - id: pyright language: system + exclude: ^tests/ diff --git a/arcade/gui/ui_manager.py b/arcade/gui/ui_manager.py index 1fb813839..e127ebdba 100644 --- a/arcade/gui/ui_manager.py +++ b/arcade/gui/ui_manager.py @@ -13,6 +13,7 @@ from pyglet.event import EVENT_HANDLED, EVENT_UNHANDLED, EventDispatcher from pyglet.input import Controller +from pyglet.math import Vec2 from typing_extensions import TypeGuard import arcade @@ -497,19 +498,19 @@ def on_disconnect(self, controller: Controller): """Called when a controller is disconnected.""" self.dispatch_ui_event(UIControllerDisconnectEvent(controller)) - def on_stick_motion(self, controller: Controller, name, value): + def on_stick_motion(self, controller: Controller, name: str, value: Vec2): return self.dispatch_ui_event(UIControllerStickEvent(controller, name, value)) - def on_trigger_motion(self, controller: Controller, name, value): + def on_trigger_motion(self, controller: Controller, name: str, value: float): return self.dispatch_ui_event(UIControllerTriggerEvent(controller, name, value)) - def on_button_press(self, controller: Controller, button): + def on_button_press(self, controller: Controller, button: str): return self.dispatch_ui_event(UIControllerButtonPressEvent(controller, button)) - def on_button_release(self, controller: Controller, button): + def on_button_release(self, controller: Controller, button: str): return self.dispatch_ui_event(UIControllerButtonReleaseEvent(controller, button)) - def on_dpad_motion(self, controller: Controller, value): + def on_dpad_motion(self, controller: Controller, value: Vec2): return self.dispatch_ui_event(UIControllerDpadEvent(controller, value)) @property diff --git a/tests/unit/gui/test_focus.py b/tests/unit/gui/test_focus.py new file mode 100644 index 000000000..ce38fc915 --- /dev/null +++ b/tests/unit/gui/test_focus.py @@ -0,0 +1,80 @@ +from pyglet.math import Vec2 + +from arcade.gui import UIFlatButton +from arcade.gui.experimental.focus import UIFocusGroup + + +def test_focus_group_no_focus_set_by_default(ui): + group = UIFocusGroup() + _ = group.add(UIFlatButton()) + + group.detect_focusable_widgets() + + assert group.focused_widget is None + +def test_focus_group_focus_set(ui): + group = UIFocusGroup() + + assert group.focused_widget is None + btn_1 = group.add(UIFlatButton()) + btn_2 = group.add(UIFlatButton()) + + group.detect_focusable_widgets() + + group.set_focus(btn_1) + + assert group.focused_widget == btn_1 + assert btn_1.focused is True + assert btn_2.focused is False + +def test_nested_groups_button_press(ui): + """ + Test when nested UIFocusGroups are used. + + The inner group should consume the focus event and not pass it to the outer group. + """ + + group_1 = ui.add(UIFocusGroup()) + btn_1 = group_1.add(UIFlatButton()) + + group_2 = group_1.add(UIFocusGroup()) + btn_2 = group_2.add(UIFlatButton()) + + group_1.detect_focusable_widgets() + group_2.detect_focusable_widgets() + + group_1.set_focus(btn_1) + group_2.set_focus(btn_2) + + ui.on_button_press(None, "a") + + assert btn_1.pressed is False + assert btn_2.pressed is True + +def test_nested_groups_dpad(ui): + """ + Test when nested UIFocusGroups are used. + + The inner group should consume the focus event and not pass it to the outer group. + """ + + group_1 = ui.add(UIFocusGroup()) + btn_1_1 = group_1.add(UIFlatButton()) + btn_1_2 = group_1.add(UIFlatButton()) + + group_2 = group_1.add(UIFocusGroup()) + btn_2_1 = group_2.add(UIFlatButton()) + btn_2_2 = group_2.add(UIFlatButton()) + + group_1.detect_focusable_widgets() + group_2.detect_focusable_widgets() + + group_1.set_focus(btn_1_1) + group_2.set_focus(btn_2_1) + + ui.on_dpad_motion(None, Vec2(0, 1)) + + assert btn_1_1.focused is True + assert btn_1_2.focused is False + assert btn_2_1.focused is False + assert btn_2_2.focused is True From 1521df6ef338ae1534815dbeed2331d344b6e7f2 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Sat, 19 Apr 2025 22:49:19 +0200 Subject: [PATCH 30/34] fix example --- arcade/examples/gui/exp_controller_inventory.py | 1 - 1 file changed, 1 deletion(-) diff --git a/arcade/examples/gui/exp_controller_inventory.py b/arcade/examples/gui/exp_controller_inventory.py index 6535e2349..4938a7050 100644 --- a/arcade/examples/gui/exp_controller_inventory.py +++ b/arcade/examples/gui/exp_controller_inventory.py @@ -413,7 +413,6 @@ def on_draw_before_ui(self): if __name__ == "__main__": # pixelate the font - pyglet.options.text_antialiasing = False pyglet.font.base.Font.texture_min_filter = GL_NEAREST pyglet.font.base.Font.texture_mag_filter = GL_NEAREST From b9bc901680f328357e749bb17acb4e5ade700316 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Sat, 19 Apr 2025 23:19:11 +0200 Subject: [PATCH 31/34] sort imports --- arcade/gui/ui_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arcade/gui/ui_manager.py b/arcade/gui/ui_manager.py index e127ebdba..6d25be8bf 100644 --- a/arcade/gui/ui_manager.py +++ b/arcade/gui/ui_manager.py @@ -22,6 +22,8 @@ from arcade.gui.events import ( UIControllerButtonPressEvent, UIControllerButtonReleaseEvent, + UIControllerConnectEvent, + UIControllerDisconnectEvent, UIControllerDpadEvent, UIControllerStickEvent, UIControllerTriggerEvent, @@ -36,8 +38,6 @@ UITextInputEvent, UITextMotionEvent, UITextMotionSelectEvent, - UIControllerConnectEvent, - UIControllerDisconnectEvent, ) from arcade.gui.surface import Surface from arcade.gui.widgets import UIWidget From f8bb36b20dc74da532be5a612835b5ece654b492 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Sat, 26 Apr 2025 23:08:00 +0200 Subject: [PATCH 32/34] gui: rename Focusable, add documentation about controller support --- .../examples/gui/exp_controller_inventory.py | 4 +- .../gui/exp_controller_support_grid.py | 6 +- arcade/gui/experimental/focus.py | 24 +-- .../gui/controller_support.rst | 198 ++++++++++++++++++ doc/programming_guide/gui/index.rst | 4 +- 5 files changed, 216 insertions(+), 20 deletions(-) create mode 100644 doc/programming_guide/gui/controller_support.rst diff --git a/arcade/examples/gui/exp_controller_inventory.py b/arcade/examples/gui/exp_controller_inventory.py index 4938a7050..11a972450 100644 --- a/arcade/examples/gui/exp_controller_inventory.py +++ b/arcade/examples/gui/exp_controller_inventory.py @@ -44,7 +44,7 @@ UIEvent, ) from arcade.gui.events import UIControllerButtonPressEvent -from arcade.gui.experimental.focus import Focusable, UIFocusGroup +from arcade.gui.experimental.focus import UIFocusable, UIFocusGroup from arcade.resources import load_kenney_fonts @@ -143,7 +143,7 @@ def legs(self, value): self[2] = value -class InventorySlotUI(Focusable, UIFlatButton): +class InventorySlotUI(UIFocusable, UIFlatButton): """Represents a single inventory slot. The slot accesses a specific index in the inventory. diff --git a/arcade/examples/gui/exp_controller_support_grid.py b/arcade/examples/gui/exp_controller_support_grid.py index dfef1ca0d..4833b0fca 100644 --- a/arcade/examples/gui/exp_controller_support_grid.py +++ b/arcade/examples/gui/exp_controller_support_grid.py @@ -20,10 +20,10 @@ UIView, UIWidget, ) -from arcade.gui.experimental.focus import Focusable, UIFocusGroup +from arcade.gui.experimental.focus import UIFocusable, UIFocusGroup -class FocusableButton(Focusable, UIFlatButton): +class FocusableButton(UIFocusable, UIFlatButton): pass @@ -43,7 +43,7 @@ def setup_grid_focus_transition(grid: Dict[Tuple[int, int], UIWidget]): for c in range(cols): for r in range(rows): btn = grid.get((c, r)) - if btn is None or not isinstance(btn, Focusable): + if btn is None or not isinstance(btn, UIFocusable): continue if c > 0: diff --git a/arcade/gui/experimental/focus.py b/arcade/gui/experimental/focus.py index 2cff7bdc6..204c8efad 100644 --- a/arcade/gui/experimental/focus.py +++ b/arcade/gui/experimental/focus.py @@ -25,7 +25,7 @@ from arcade.gui.widgets.slider import UIBaseSlider -class Focusable(UIWidget): +class UIFocusable(UIWidget): """ A widget that provides additional information about focus neighbors. @@ -98,12 +98,12 @@ def on_event(self, event: UIEvent) -> Optional[bool]: return EVENT_HANDLED elif event.symbol == arcade.key.SPACE: - self.start_interaction() + self._start_interaction() return EVENT_HANDLED elif isinstance(event, UIKeyReleaseEvent): if event.symbol == arcade.key.SPACE: - self.end_interaction() + self._end_interaction() return EVENT_HANDLED elif isinstance(event, UIControllerDpadEvent): @@ -140,11 +140,11 @@ def on_event(self, event: UIEvent) -> Optional[bool]: elif isinstance(event, UIControllerButtonPressEvent): if event.button == "a": - self.start_interaction() + self._start_interaction() return EVENT_HANDLED elif isinstance(event, UIControllerButtonReleaseEvent): if event.button == "a": - self.end_interaction() + self._end_interaction() return EVENT_HANDLED return EVENT_UNHANDLED @@ -213,7 +213,7 @@ def set_focus(self, widget: UIWidget | None | EllipsisType = ...): def focus_up(self): widget = self.focused_widget - if isinstance(widget, Focusable): + if isinstance(widget, UIFocusable): if widget.neighbor_up: self.set_focus(widget.neighbor_up) return @@ -222,7 +222,7 @@ def focus_up(self): def focus_down(self): widget = self.focused_widget - if isinstance(widget, Focusable): + if isinstance(widget, UIFocusable): if widget.neighbor_down: self.set_focus(widget.neighbor_down) return @@ -231,7 +231,7 @@ def focus_down(self): def focus_left(self): widget = self.focused_widget - if isinstance(widget, Focusable): + if isinstance(widget, UIFocusable): if widget.neighbor_left: self.set_focus(widget.neighbor_left) return @@ -240,7 +240,7 @@ def focus_left(self): def focus_right(self): widget = self.focused_widget - if isinstance(widget, Focusable): + if isinstance(widget, UIFocusable): if widget.neighbor_right: self.set_focus(widget.neighbor_right) return @@ -275,7 +275,7 @@ def focus_previous(self): # automatically wrap around via index -1 self.set_focus(self._focusable_widgets[focused_index]) - def start_interaction(self): + def _start_interaction(self): # TODO this should be handled in the widget widget = self.focused_widget @@ -294,7 +294,7 @@ def start_interaction(self): else: print("Cannot interact widget") - def end_interaction(self): + def _end_interaction(self): widget = self.focused_widget if isinstance(widget, UIInteractiveWidget): @@ -334,7 +334,7 @@ def do_post_render(self, surface: Surface): if self._debug: # debugging - if isinstance(widget, Focusable): + if isinstance(widget, UIFocusable): if widget.neighbor_up: self._draw_indicator( widget.rect.top_center, diff --git a/doc/programming_guide/gui/controller_support.rst b/doc/programming_guide/gui/controller_support.rst new file mode 100644 index 000000000..d443632f0 --- /dev/null +++ b/doc/programming_guide/gui/controller_support.rst @@ -0,0 +1,198 @@ +.. _gui_controller_support: + +GUI Controller Support +---------------------- + +The `arcade.gui` module now includes **experimental controller support**, allowing you to navigate through GUI elements using a game controller. This feature introduces the `ControllerWindow` and `ControllerView` classes, which provide controller-specific functionality. + +Below is a guide on how to set up and use this feature effectively. + +Basic Setup +~~~~~~~~~~~ + +To use controller support, you need to use the `ControllerWindow` and `ControllerView` classes. +These classes provide the necessary hooks for handling controller input and managing focus within the GUI. + +The following code makes use of the `UIView` class, which simplifies the process of setting up a view with a `UIManager`. + +Setting Up Controller Support +````````````````````````````` + +The `ControllerWindow` is an instance of `arcade.Window` that integrates controller input handling. The `ControllerView` class provides controller-specific callbacks, +which are used by the `UIManager` to handle controller events. + +Below is an example of how to set up a controller-enabled application: + +.. code-block:: python + + import arcade + from arcade.gui import UIView + from arcade.experimental.controller_window import ControllerWindow, ControllerView + + + class MyControllerView(ControllerView, UIView): + def __init__(self): + super().__init__() + + # Initialize your GUI elements here + + # react to controller events for your game + def on_connect(self, controller): + print(f"Controller connected: {controller}") + + def on_disconnect(self, controller): + print(f"Controller disconnected: {controller}") + + def on_stick_motion(self, controller, stick, value): + print(f"Stick {stick} moved to {value} on controller {controller}") + + def on_trigger_motion(self, controller, trigger, value): + print(f"Trigger {trigger} moved to {value} on controller {controller}") + + def on_button_press(self, controller, button): + print(f"Button {button} pressed on controller {controller}") + + def on_button_release(self, controller, button): + print(f"Button {button} released on controller {controller}") + + def on_dpad_motion(self, controller, value): + print(f"D-Pad moved to {value} on controller {controller}") + + + if __name__ == "__main__": + window = ControllerWindow(title="Controller Support Example") + view = MyControllerView() + window.show_view(view) + arcade.run() + + +Managing Focus with `FocusGroups` +````````````````````````````````` + +To enable controller navigation, you must group your interactive GUI elements into a `UIFocusGroup`. +A `UIFocusGroup` allows the controller to cycle through the elements and ensures that only one element is focused at a time. + +A single `UIFocusGroup` can be added to the `UIManager` as a root widget acting as a `UIAnchorLayout`. + +.. code-block:: python + + from arcade.experimental.controller_window import ControllerView, ControllerWindow + from arcade.gui import UIFlatButton, UIBoxLayout, UIView + from arcade.gui.experimental.focus import UIFocusGroup + + + class MyControllerView(ControllerView, UIView): + def __init__(self): + super().__init__() + + # Create buttons and add them to the focus group + fg = UIFocusGroup() + self.ui.add(fg) + + box = UIBoxLayout() + fg.add(box) + + button1 = UIFlatButton(text="Button 1", width=200) + button2 = UIFlatButton(text="Button 2", width=200) + + box.add(button1) + box.add(button2) + + # initialize the focus group, detect focusable widgets and set the initial focus + fg.detect_focusable_widgets() + fg.set_focus() + + + if __name__ == "__main__": + window = ControllerWindow(title="Controller Support Example") + view = MyControllerView() + window.show_view(view) + window.run() + + +Setting Initial Focus +````````````````````` + +It is essential to set the initial focus for the `UIFocusGroup`. Without this, the controller will not know which element to start with. + +.. code-block:: python + + # Set the initial focus + self.focus_group.set_focus() + +Summary +``````` +To use the experimental controller support in `arcade.gui`: + +1. Use `ControllerWindow` as your main application window. +2. Use `ControllerView` to provide controller-specific callbacks for the `UIManager`. +3. Group interactive elements into a `UIFocusGroup` for navigation. +4. Set the initial focus for the `UIFocusGroup` to enable proper navigation. + +This setup allows you to create a fully functional GUI that can be navigated using a game controller. Note that this feature is experimental and may be subject to changes in future releases. + + +Advanced Usage +~~~~~~~~~~~~~~ + +Nested `UIFocusGroups` +`````````````````````` + +When using nested `UIFocusGroups`, only one `UIFocusGroup` will be navigated at a time. +This is particularly useful for scenarios like modals or overlays, where you want to temporarily restrict navigation to +a specific set of elements. For example, the `UIDropdown` widget uses this feature to handle focus within its dropdown +menu while isolating it from the rest of the interface. + + +Advanced focus direction +```````````````````````` + +To provide a more advanced focus direction, you can use the `UIFocusable` class. + +The `UIFocusable` class allows you to define directional neighbors (`neighbor_up`, `neighbor_down`, `neighbor_left`, `neighbor_right`) for a widget. +These neighbors determine how focus moves between widgets when navigating with a controller or keyboard. + +Here is an example of how to use the `UIFocusable` class: + +.. code-block:: python + + from arcade.gui import UIFlatButton, UIGridLayout + from arcade.gui.experimental.focus import UIFocusGroup, UIFocusable + + class MyButton(UIFlatButton, UIFocusable): + def __init__(self, text, width): + super().__init__(text=text, width=width) + + + # Create focusable buttons + button1 = MyButton(text="Button 1", width=200) + button2 = MyButton(text="Button 2", width=200) + button3 = MyButton(text="Button 3", width=200) + button4 = MyButton(text="Button 4", width=200) + + # Set directional neighbors + button1.neighbor_right = button2 + button1.neighbor_down = button3 + button2.neighbor_left = button1 + button2.neighbor_down = button4 + button3.neighbor_up = button1 + button3.neighbor_right = button4 + button4.neighbor_up = button2 + button4.neighbor_left = button3 + + # Add buttons to a focus group + fg = UIFocusGroup() + + grid_layout = UIGridLayout(column_count=2, row_count=2, vertical_spacing=10) + grid_layout.add(button1, col_num=0, row_num=0) + grid_layout.add(button2, col_num=1, row_num=0) + grid_layout.add(button3, col_num=0, row_num=1) + grid_layout.add(button4, col_num=1, row_num=1) + + fg.add(grid_layout) + + # Detect focusable widgets and set the initial focus + fg.detect_focusable_widgets() + fg.set_focus(button1) + +This setup allows you to define custom navigation paths between widgets, enabling more complex focus behavior. diff --git a/doc/programming_guide/gui/index.rst b/doc/programming_guide/gui/index.rst index 44129d87b..c3a1179a1 100644 --- a/doc/programming_guide/gui/index.rst +++ b/doc/programming_guide/gui/index.rst @@ -35,6 +35,4 @@ Find the required information in the following sections: style own_widgets own_layout - - - + controller_support From ed17c937dd6fa7ebc59a2f56971fe1e2596ad46e Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Sat, 26 Apr 2025 23:12:34 +0200 Subject: [PATCH 33/34] changelog: add experimental controller support with documentation --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6defbb42e..e404b2222 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Arcade [PyPi Release History](https://pypi.org/project/arcade/#history) page. - GUI - Fix `UIScrollArea.add` always returning None - Support `layer` in `UIView.add_widget()` + - Experimental controller support (incl. documentation) - Text objects are now lazy and can be created before the window ## Version 3.1.0 From fa50b44760ff343627ae34874613ff56b60f76b5 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Sun, 4 May 2025 21:56:50 +0200 Subject: [PATCH 34/34] merge development, fix some naming --- arcade/gui/widgets/slider.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arcade/gui/widgets/slider.py b/arcade/gui/widgets/slider.py index e3b7d5b49..9d7fccede 100644 --- a/arcade/gui/widgets/slider.py +++ b/arcade/gui/widgets/slider.py @@ -114,9 +114,9 @@ def _apply_step(self, value: float): return value - def _change_value(self, value: float): - # TODO changing the value itself should trigger this event - # current problem is, that the property does not pass the old value to change listeners + def _set_value(self, value: float): + # TODO changing the value itself should trigger `on_change` event + # current problem is, that the property does not pass the old value to listeners if value < self.min_value: value = self.min_value elif value > self.max_value: @@ -145,7 +145,7 @@ def norm_value(self): def norm_value(self, value): """Normalized value between 0.0 and 1.0""" new_value = min(value * (self.max_value - self.min_value) + self.min_value, self.max_value) - self._change_value(new_value) + self._set_value(new_value) @property def _thumb_x(self):