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    def __del__(self) -> None:
111        bs.release_gamepad_input()
112
113    @override
114    def get_main_window_state(self) -> bui.MainWindowState:
115        # Support recreating our window for back/refresh purposes.
116        cls = type(self)
117        return bui.BasicMainWindowState(
118            create_call=lambda transition, origin_widget: cls(
119                transition=transition, origin_widget=origin_widget
120            )
121        )
122
123    def gamepad_configure_callback(self, event: dict[str, Any]) -> None:
124        """Respond to a gamepad button press during config selection."""
125        from bauiv1lib.settings.gamepad import GamepadSettingsWindow
126
127        if not self.main_window_has_control():
128            return
129
130        # Ignore all but button-presses.
131        if event['type'] not in ['BUTTONDOWN', 'HATMOTION']:
132            return
133        bs.release_gamepad_input()
134
135        assert bui.app.classic is not None
136
137        bui.getsound('activateBeep').play()
138        bui.getsound('swish').play()
139        device = event['input_device']
140        assert isinstance(device, bs.InputDevice)
141
142        # No matter where we redirect to, we want their back
143        # functionality to skip over us and go to our parent.
144        assert self.main_window_back_state is not None
145        back_state = self.main_window_back_state
146
147        if device.allows_configuring:
148            self.main_window_replace(
149                GamepadSettingsWindow(device), back_state=back_state
150            )
151        else:
152            self.main_window_replace(
153                _NotConfigurableWindow(device), back_state=back_state
154            )
155
156
157class _NotConfigurableWindow(bui.MainWindow):
158
159    def __init__(
160        self,
161        device: bs.InputDevice,
162        transition: str | None = 'in_right',
163        origin_widget: bui.Widget | None = None,
164    ) -> None:
165        width = 700
166        height = 200
167        button_width = 80
168        uiscale = bui.app.ui_v1.uiscale
169        super().__init__(
170            root_widget=bui.containerwidget(
171                scale=(
172                    1.7
173                    if uiscale is bui.UIScale.SMALL
174                    else (1.4 if uiscale is bui.UIScale.MEDIUM else 1.0)
175                ),
176                size=(width, height),
177            ),
178            transition=transition,
179            origin_widget=origin_widget,
180        )
181        self.device = device
182
183        if device.allows_configuring_in_system_settings:
184            msg = bui.Lstr(
185                resource='configureDeviceInSystemSettingsText',
186                subs=[('${DEVICE}', device.name)],
187            )
188        elif device.is_controller_app:
189            msg = bui.Lstr(
190                resource='bsRemoteConfigureInAppText',
191                subs=[
192                    (
193                        '${REMOTE_APP_NAME}',
194                        bui.get_remote_app_name(),
195                    )
196                ],
197            )
198        else:
199            msg = bui.Lstr(
200                resource='cantConfigureDeviceText',
201                subs=[('${DEVICE}', device.name)],
202            )
203        bui.textwidget(
204            parent=self._root_widget,
205            position=(0, height - 80),
206            size=(width, 25),
207            text=msg,
208            scale=0.8,
209            h_align='center',
210            v_align='top',
211        )
212
213        btn = bui.buttonwidget(
214            parent=self._root_widget,
215            position=((width - button_width) / 2, 20),
216            size=(button_width, 60),
217            label=bui.Lstr(resource='okText'),
218            on_activate_call=self.main_window_back,
219        )
220        bui.containerwidget(edit=self._root_widget, cancel_button=btn)
221
222    @override
223    def get_main_window_state(self) -> bui.MainWindowState:
224        # Support recreating our window for back/refresh purposes.
225        cls = type(self)
226
227        # Pull stuff out of self here; if we do it in the lambda we'll
228        # keep self alive which we don't want.
229        device = self.device
230
231        return bui.BasicMainWindowState(
232            create_call=lambda transition, origin_widget: cls(
233                device=device,
234                transition=transition,
235                origin_widget=origin_widget,
236            )
237        )
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    def __del__(self) -> None:
112        bs.release_gamepad_input()
113
114    @override
115    def get_main_window_state(self) -> bui.MainWindowState:
116        # Support recreating our window for back/refresh purposes.
117        cls = type(self)
118        return bui.BasicMainWindowState(
119            create_call=lambda transition, origin_widget: cls(
120                transition=transition, origin_widget=origin_widget
121            )
122        )
123
124    def gamepad_configure_callback(self, event: dict[str, Any]) -> None:
125        """Respond to a gamepad button press during config selection."""
126        from bauiv1lib.settings.gamepad import GamepadSettingsWindow
127
128        if not self.main_window_has_control():
129            return
130
131        # Ignore all but button-presses.
132        if event['type'] not in ['BUTTONDOWN', 'HATMOTION']:
133            return
134        bs.release_gamepad_input()
135
136        assert bui.app.classic is not None
137
138        bui.getsound('activateBeep').play()
139        bui.getsound('swish').play()
140        device = event['input_device']
141        assert isinstance(device, bs.InputDevice)
142
143        # No matter where we redirect to, we want their back
144        # functionality to skip over us and go to our parent.
145        assert self.main_window_back_state is not None
146        back_state = self.main_window_back_state
147
148        if device.allows_configuring:
149            self.main_window_replace(
150                GamepadSettingsWindow(device), back_state=back_state
151            )
152        else:
153            self.main_window_replace(
154                _NotConfigurableWindow(device), back_state=back_state
155            )

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:
114    @override
115    def get_main_window_state(self) -> bui.MainWindowState:
116        # Support recreating our window for back/refresh purposes.
117        cls = type(self)
118        return bui.BasicMainWindowState(
119            create_call=lambda transition, origin_widget: cls(
120                transition=transition, origin_widget=origin_widget
121            )
122        )

Return a WindowState to recreate this window, if supported.

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

Respond to a gamepad button press during config selection.