bauiv1lib.settings.gamepadselect

Settings UI related to gamepad functionality.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Settings UI related to gamepad functionality."""
  4
  5from __future__ import annotations
  6
  7from typing import TYPE_CHECKING, override
  8
  9import bascenev1 as bs
 10import bauiv1 as bui
 11
 12if TYPE_CHECKING:
 13    from typing import Any
 14
 15
 16class GamepadSelectWindow(bui.MainWindow):
 17    """Window for selecting a gamepad to configure."""
 18
 19    def __init__(
 20        self,
 21        transition: str | None = 'in_right',
 22        origin_widget: bui.Widget | None = None,
 23    ) -> None:
 24        from typing import cast
 25
 26        width = 480
 27        height = 170
 28        spacing = 40
 29        self._r = 'configGamepadSelectWindow'
 30
 31        assert bui.app.classic is not None
 32        uiscale = bui.app.ui_v1.uiscale
 33        super().__init__(
 34            root_widget=bui.containerwidget(
 35                scale=(
 36                    2.3
 37                    if uiscale is bui.UIScale.SMALL
 38                    else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
 39                ),
 40                size=(width, height),
 41            ),
 42            transition=transition,
 43            origin_widget=origin_widget,
 44        )
 45
 46        btn = bui.buttonwidget(
 47            parent=self._root_widget,
 48            position=(20, height - 60),
 49            size=(130, 60),
 50            label=bui.Lstr(resource='backText'),
 51            button_type='back',
 52            scale=0.8,
 53            on_activate_call=self.main_window_back,
 54        )
 55
 56        # Let's not have anything selected by default; its misleading
 57        # looking for the controller getting configured.
 58        bui.containerwidget(
 59            edit=self._root_widget,
 60            cancel_button=btn,
 61            selected_child=cast(bui.Widget, 0),
 62        )
 63        bui.textwidget(
 64            parent=self._root_widget,
 65            position=(20, height - 50),
 66            size=(width, 25),
 67            text=bui.Lstr(resource=f'{self._r}.titleText'),
 68            maxwidth=250,
 69            color=bui.app.ui_v1.title_color,
 70            h_align='center',
 71            v_align='center',
 72        )
 73
 74        bui.buttonwidget(
 75            edit=btn,
 76            button_type='backSmall',
 77            size=(60, 60),
 78            label=bui.charstr(bui.SpecialChar.BACK),
 79        )
 80
 81        v: float = height - 60
 82        v -= spacing
 83        bui.textwidget(
 84            parent=self._root_widget,
 85            position=(15, v),
 86            size=(width - 30, 30),
 87            scale=0.8,
 88            text=bui.Lstr(resource=f'{self._r}.pressAnyButtonText'),
 89            maxwidth=width * 0.95,
 90            color=bui.app.ui_v1.infotextcolor,
 91            h_align='center',
 92            v_align='top',
 93        )
 94        v -= spacing * 1.24
 95        if bui.app.classic.platform == 'android':
 96            bui.textwidget(
 97                parent=self._root_widget,
 98                position=(15, v),
 99                size=(width - 30, 30),
100                scale=0.46,
101                text=bui.Lstr(resource=f'{self._r}.androidNoteText'),
102                maxwidth=width * 0.95,
103                color=(0.7, 0.9, 0.7, 0.5),
104                h_align='center',
105                v_align='top',
106            )
107
108        bs.capture_gamepad_input(bui.WeakCall(self.gamepad_configure_callback))
109
110    @override
111    def __del__(self) -> None:
112        super().__del__()
113        bs.release_gamepad_input()
114
115    @override
116    def get_main_window_state(self) -> bui.MainWindowState:
117        # Support recreating our window for back/refresh purposes.
118        cls = type(self)
119        return bui.BasicMainWindowState(
120            create_call=lambda transition, origin_widget: cls(
121                transition=transition, origin_widget=origin_widget
122            )
123        )
124
125    def gamepad_configure_callback(self, event: dict[str, Any]) -> None:
126        """Respond to a gamepad button press during config selection."""
127        from bauiv1lib.settings.gamepad import GamepadSettingsWindow
128
129        if not self.main_window_has_control():
130            return
131
132        # Ignore all but button-presses.
133        if event['type'] not in ['BUTTONDOWN', 'HATMOTION']:
134            return
135        bs.release_gamepad_input()
136
137        assert bui.app.classic is not None
138
139        bui.getsound('activateBeep').play()
140        bui.getsound('swish').play()
141        device = event['input_device']
142        assert isinstance(device, bs.InputDevice)
143
144        # No matter where we redirect to, we want their back
145        # functionality to skip over us and go to our parent.
146        assert self.main_window_back_state is not None
147        back_state = self.main_window_back_state
148
149        if device.allows_configuring:
150            self.main_window_replace(
151                GamepadSettingsWindow(device), back_state=back_state
152            )
153        else:
154            self.main_window_replace(
155                _NotConfigurableWindow(device), back_state=back_state
156            )
157
158
159class _NotConfigurableWindow(bui.MainWindow):
160
161    def __init__(
162        self,
163        device: bs.InputDevice,
164        transition: str | None = 'in_right',
165        origin_widget: bui.Widget | None = None,
166    ) -> None:
167        width = 700
168        height = 200
169        button_width = 80
170        uiscale = bui.app.ui_v1.uiscale
171        super().__init__(
172            root_widget=bui.containerwidget(
173                scale=(
174                    1.7
175                    if uiscale is bui.UIScale.SMALL
176                    else (1.4 if uiscale is bui.UIScale.MEDIUM else 1.0)
177                ),
178                size=(width, height),
179            ),
180            transition=transition,
181            origin_widget=origin_widget,
182        )
183        self.device = device
184
185        if device.allows_configuring_in_system_settings:
186            msg = bui.Lstr(
187                resource='configureDeviceInSystemSettingsText',
188                subs=[('${DEVICE}', device.name)],
189            )
190        elif device.is_controller_app:
191            msg = bui.Lstr(
192                resource='bsRemoteConfigureInAppText',
193                subs=[
194                    (
195                        '${REMOTE_APP_NAME}',
196                        bui.get_remote_app_name(),
197                    )
198                ],
199            )
200        else:
201            msg = bui.Lstr(
202                resource='cantConfigureDeviceText',
203                subs=[('${DEVICE}', device.name)],
204            )
205        bui.textwidget(
206            parent=self._root_widget,
207            position=(0, height - 80),
208            size=(width, 25),
209            text=msg,
210            scale=0.8,
211            h_align='center',
212            v_align='top',
213        )
214
215        btn = bui.buttonwidget(
216            parent=self._root_widget,
217            position=((width - button_width) / 2, 20),
218            size=(button_width, 60),
219            label=bui.Lstr(resource='okText'),
220            on_activate_call=self.main_window_back,
221        )
222        bui.containerwidget(edit=self._root_widget, cancel_button=btn)
223
224    @override
225    def get_main_window_state(self) -> bui.MainWindowState:
226        # Support recreating our window for back/refresh purposes.
227        cls = type(self)
228
229        # Pull stuff out of self here; if we do it in the lambda we'll
230        # keep self alive which we don't want.
231        device = self.device
232
233        return bui.BasicMainWindowState(
234            create_call=lambda transition, origin_widget: cls(
235                device=device,
236                transition=transition,
237                origin_widget=origin_widget,
238            )
239        )
class GamepadSelectWindow(bauiv1._uitypes.MainWindow):
 17class GamepadSelectWindow(bui.MainWindow):
 18    """Window for selecting a gamepad to configure."""
 19
 20    def __init__(
 21        self,
 22        transition: str | None = 'in_right',
 23        origin_widget: bui.Widget | None = None,
 24    ) -> None:
 25        from typing import cast
 26
 27        width = 480
 28        height = 170
 29        spacing = 40
 30        self._r = 'configGamepadSelectWindow'
 31
 32        assert bui.app.classic is not None
 33        uiscale = bui.app.ui_v1.uiscale
 34        super().__init__(
 35            root_widget=bui.containerwidget(
 36                scale=(
 37                    2.3
 38                    if uiscale is bui.UIScale.SMALL
 39                    else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
 40                ),
 41                size=(width, height),
 42            ),
 43            transition=transition,
 44            origin_widget=origin_widget,
 45        )
 46
 47        btn = bui.buttonwidget(
 48            parent=self._root_widget,
 49            position=(20, height - 60),
 50            size=(130, 60),
 51            label=bui.Lstr(resource='backText'),
 52            button_type='back',
 53            scale=0.8,
 54            on_activate_call=self.main_window_back,
 55        )
 56
 57        # Let's not have anything selected by default; its misleading
 58        # looking for the controller getting configured.
 59        bui.containerwidget(
 60            edit=self._root_widget,
 61            cancel_button=btn,
 62            selected_child=cast(bui.Widget, 0),
 63        )
 64        bui.textwidget(
 65            parent=self._root_widget,
 66            position=(20, height - 50),
 67            size=(width, 25),
 68            text=bui.Lstr(resource=f'{self._r}.titleText'),
 69            maxwidth=250,
 70            color=bui.app.ui_v1.title_color,
 71            h_align='center',
 72            v_align='center',
 73        )
 74
 75        bui.buttonwidget(
 76            edit=btn,
 77            button_type='backSmall',
 78            size=(60, 60),
 79            label=bui.charstr(bui.SpecialChar.BACK),
 80        )
 81
 82        v: float = height - 60
 83        v -= spacing
 84        bui.textwidget(
 85            parent=self._root_widget,
 86            position=(15, v),
 87            size=(width - 30, 30),
 88            scale=0.8,
 89            text=bui.Lstr(resource=f'{self._r}.pressAnyButtonText'),
 90            maxwidth=width * 0.95,
 91            color=bui.app.ui_v1.infotextcolor,
 92            h_align='center',
 93            v_align='top',
 94        )
 95        v -= spacing * 1.24
 96        if bui.app.classic.platform == 'android':
 97            bui.textwidget(
 98                parent=self._root_widget,
 99                position=(15, v),
100                size=(width - 30, 30),
101                scale=0.46,
102                text=bui.Lstr(resource=f'{self._r}.androidNoteText'),
103                maxwidth=width * 0.95,
104                color=(0.7, 0.9, 0.7, 0.5),
105                h_align='center',
106                v_align='top',
107            )
108
109        bs.capture_gamepad_input(bui.WeakCall(self.gamepad_configure_callback))
110
111    @override
112    def __del__(self) -> None:
113        super().__del__()
114        bs.release_gamepad_input()
115
116    @override
117    def get_main_window_state(self) -> bui.MainWindowState:
118        # Support recreating our window for back/refresh purposes.
119        cls = type(self)
120        return bui.BasicMainWindowState(
121            create_call=lambda transition, origin_widget: cls(
122                transition=transition, origin_widget=origin_widget
123            )
124        )
125
126    def gamepad_configure_callback(self, event: dict[str, Any]) -> None:
127        """Respond to a gamepad button press during config selection."""
128        from bauiv1lib.settings.gamepad import GamepadSettingsWindow
129
130        if not self.main_window_has_control():
131            return
132
133        # Ignore all but button-presses.
134        if event['type'] not in ['BUTTONDOWN', 'HATMOTION']:
135            return
136        bs.release_gamepad_input()
137
138        assert bui.app.classic is not None
139
140        bui.getsound('activateBeep').play()
141        bui.getsound('swish').play()
142        device = event['input_device']
143        assert isinstance(device, bs.InputDevice)
144
145        # No matter where we redirect to, we want their back
146        # functionality to skip over us and go to our parent.
147        assert self.main_window_back_state is not None
148        back_state = self.main_window_back_state
149
150        if device.allows_configuring:
151            self.main_window_replace(
152                GamepadSettingsWindow(device), back_state=back_state
153            )
154        else:
155            self.main_window_replace(
156                _NotConfigurableWindow(device), back_state=back_state
157            )

Window for selecting a gamepad to configure.

GamepadSelectWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
 20    def __init__(
 21        self,
 22        transition: str | None = 'in_right',
 23        origin_widget: bui.Widget | None = None,
 24    ) -> None:
 25        from typing import cast
 26
 27        width = 480
 28        height = 170
 29        spacing = 40
 30        self._r = 'configGamepadSelectWindow'
 31
 32        assert bui.app.classic is not None
 33        uiscale = bui.app.ui_v1.uiscale
 34        super().__init__(
 35            root_widget=bui.containerwidget(
 36                scale=(
 37                    2.3
 38                    if uiscale is bui.UIScale.SMALL
 39                    else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
 40                ),
 41                size=(width, height),
 42            ),
 43            transition=transition,
 44            origin_widget=origin_widget,
 45        )
 46
 47        btn = bui.buttonwidget(
 48            parent=self._root_widget,
 49            position=(20, height - 60),
 50            size=(130, 60),
 51            label=bui.Lstr(resource='backText'),
 52            button_type='back',
 53            scale=0.8,
 54            on_activate_call=self.main_window_back,
 55        )
 56
 57        # Let's not have anything selected by default; its misleading
 58        # looking for the controller getting configured.
 59        bui.containerwidget(
 60            edit=self._root_widget,
 61            cancel_button=btn,
 62            selected_child=cast(bui.Widget, 0),
 63        )
 64        bui.textwidget(
 65            parent=self._root_widget,
 66            position=(20, height - 50),
 67            size=(width, 25),
 68            text=bui.Lstr(resource=f'{self._r}.titleText'),
 69            maxwidth=250,
 70            color=bui.app.ui_v1.title_color,
 71            h_align='center',
 72            v_align='center',
 73        )
 74
 75        bui.buttonwidget(
 76            edit=btn,
 77            button_type='backSmall',
 78            size=(60, 60),
 79            label=bui.charstr(bui.SpecialChar.BACK),
 80        )
 81
 82        v: float = height - 60
 83        v -= spacing
 84        bui.textwidget(
 85            parent=self._root_widget,
 86            position=(15, v),
 87            size=(width - 30, 30),
 88            scale=0.8,
 89            text=bui.Lstr(resource=f'{self._r}.pressAnyButtonText'),
 90            maxwidth=width * 0.95,
 91            color=bui.app.ui_v1.infotextcolor,
 92            h_align='center',
 93            v_align='top',
 94        )
 95        v -= spacing * 1.24
 96        if bui.app.classic.platform == 'android':
 97            bui.textwidget(
 98                parent=self._root_widget,
 99                position=(15, v),
100                size=(width - 30, 30),
101                scale=0.46,
102                text=bui.Lstr(resource=f'{self._r}.androidNoteText'),
103                maxwidth=width * 0.95,
104                color=(0.7, 0.9, 0.7, 0.5),
105                h_align='center',
106                v_align='top',
107            )
108
109        bs.capture_gamepad_input(bui.WeakCall(self.gamepad_configure_callback))

Create a MainWindow given a root widget and transition info.

Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it.

@override
def get_main_window_state(self) -> bauiv1.MainWindowState:
116    @override
117    def get_main_window_state(self) -> bui.MainWindowState:
118        # Support recreating our window for back/refresh purposes.
119        cls = type(self)
120        return bui.BasicMainWindowState(
121            create_call=lambda transition, origin_widget: cls(
122                transition=transition, origin_widget=origin_widget
123            )
124        )

Return a WindowState to recreate this window, if supported.

def gamepad_configure_callback(self, event: dict[str, typing.Any]) -> None:
126    def gamepad_configure_callback(self, event: dict[str, Any]) -> None:
127        """Respond to a gamepad button press during config selection."""
128        from bauiv1lib.settings.gamepad import GamepadSettingsWindow
129
130        if not self.main_window_has_control():
131            return
132
133        # Ignore all but button-presses.
134        if event['type'] not in ['BUTTONDOWN', 'HATMOTION']:
135            return
136        bs.release_gamepad_input()
137
138        assert bui.app.classic is not None
139
140        bui.getsound('activateBeep').play()
141        bui.getsound('swish').play()
142        device = event['input_device']
143        assert isinstance(device, bs.InputDevice)
144
145        # No matter where we redirect to, we want their back
146        # functionality to skip over us and go to our parent.
147        assert self.main_window_back_state is not None
148        back_state = self.main_window_back_state
149
150        if device.allows_configuring:
151            self.main_window_replace(
152                GamepadSettingsWindow(device), back_state=back_state
153            )
154        else:
155            self.main_window_replace(
156                _NotConfigurableWindow(device), back_state=back_state
157            )

Respond to a gamepad button press during config selection.