bauiv1lib.settings.keyboard

Keyboard settings related UI functionality.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Keyboard settings related UI functionality."""
  4
  5from __future__ import annotations
  6
  7from typing import TYPE_CHECKING
  8
  9from bauiv1lib.popup import PopupMenuWindow
 10import bauiv1 as bui
 11import bascenev1 as bs
 12
 13if TYPE_CHECKING:
 14    from typing import Any
 15    from bauiv1lib.popup import PopupWindow
 16
 17
 18class ConfigKeyboardWindow(bui.Window):
 19    """Window for configuring keyboards."""
 20
 21    def __init__(self, c: bs.InputDevice, transition: str = 'in_right'):
 22        self._r = 'configKeyboardWindow'
 23        self._input = c
 24        self._name = self._input.name
 25        self._unique_id = self._input.unique_identifier
 26        dname_raw = self._name
 27        if self._unique_id != '#1':
 28            dname_raw += ' ' + self._unique_id.replace('#', 'P')
 29        self._displayname = bui.Lstr(translate=('inputDeviceNames', dname_raw))
 30        self._width = 700
 31        if self._unique_id != '#1':
 32            self._height = 480
 33        else:
 34            self._height = 375
 35        self._spacing = 40
 36        assert bui.app.classic is not None
 37        uiscale = bui.app.ui_v1.uiscale
 38        super().__init__(
 39            root_widget=bui.containerwidget(
 40                size=(self._width, self._height),
 41                scale=(
 42                    1.6
 43                    if uiscale is bui.UIScale.SMALL
 44                    else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
 45                ),
 46                stack_offset=(0, 5) if uiscale is bui.UIScale.SMALL else (0, 0),
 47                transition=transition,
 48            )
 49        )
 50
 51        self._settings: dict[str, int] = {}
 52        self._get_config_mapping()
 53
 54        self._rebuild_ui()
 55
 56    def _get_config_mapping(self, default: bool = False) -> None:
 57        for button in [
 58            'buttonJump',
 59            'buttonPunch',
 60            'buttonBomb',
 61            'buttonPickUp',
 62            'buttonStart',
 63            'buttonStart2',
 64            'buttonUp',
 65            'buttonDown',
 66            'buttonLeft',
 67            'buttonRight',
 68        ]:
 69            assert bui.app.classic is not None
 70            self._settings[button] = (
 71                bui.app.classic.get_input_device_mapped_value(
 72                    self._input, button, default
 73                )
 74            )
 75
 76    def _rebuild_ui(self, is_reset: bool = False) -> None:
 77        assert bui.app.classic is not None
 78
 79        for widget in self._root_widget.get_children():
 80            widget.delete()
 81
 82        # b_off = 0 if self._unique_id != '#1' else 9
 83        cancel_button = bui.buttonwidget(
 84            parent=self._root_widget,
 85            autoselect=True,
 86            position=(38, self._height - 85),
 87            size=(170, 60),
 88            label=bui.Lstr(resource='cancelText'),
 89            scale=0.9,
 90            on_activate_call=self._cancel,
 91        )
 92        save_button = bui.buttonwidget(
 93            parent=self._root_widget,
 94            autoselect=True,
 95            position=(self._width - 190, self._height - 85),
 96            size=(180, 60),
 97            label=bui.Lstr(resource='saveText'),
 98            scale=0.9,
 99            text_scale=0.9,
100            on_activate_call=self._save,
101        )
102        bui.containerwidget(
103            edit=self._root_widget,
104            cancel_button=cancel_button,
105            start_button=save_button,
106        )
107
108        v = self._height - 74.0
109        bui.textwidget(
110            parent=self._root_widget,
111            position=(self._width * 0.5, v + 15),
112            size=(0, 0),
113            text=bui.Lstr(
114                resource=self._r + '.configuringText',
115                subs=[('${DEVICE}', self._displayname)],
116            ),
117            color=bui.app.ui_v1.title_color,
118            h_align='center',
119            v_align='center',
120            maxwidth=270,
121            scale=0.83,
122        )
123        v -= 20
124
125        if self._unique_id != '#1':
126            v -= 20
127            v -= self._spacing
128            bui.textwidget(
129                parent=self._root_widget,
130                position=(0, v + 19),
131                size=(self._width, 50),
132                text=bui.Lstr(resource=self._r + '.keyboard2NoteText'),
133                scale=0.7,
134                maxwidth=self._width * 0.75,
135                max_height=110,
136                color=bui.app.ui_v1.infotextcolor,
137                h_align='center',
138                v_align='top',
139            )
140            v -= 40
141        v -= 10
142        v -= self._spacing * 2.2
143        v += 25
144        v -= 42
145        h_offs = 160
146        dist = 70
147        d_color = (0.4, 0.4, 0.8)
148        self._capture_button(
149            pos=(h_offs, v + 0.95 * dist),
150            color=d_color,
151            button='buttonUp',
152            texture=bui.gettexture('upButton'),
153            scale=1.0,
154        )
155        self._capture_button(
156            pos=(h_offs - 1.2 * dist, v),
157            color=d_color,
158            button='buttonLeft',
159            texture=bui.gettexture('leftButton'),
160            scale=1.0,
161        )
162        self._capture_button(
163            pos=(h_offs + 1.2 * dist, v),
164            color=d_color,
165            button='buttonRight',
166            texture=bui.gettexture('rightButton'),
167            scale=1.0,
168        )
169        self._capture_button(
170            pos=(h_offs, v - 0.95 * dist),
171            color=d_color,
172            button='buttonDown',
173            texture=bui.gettexture('downButton'),
174            scale=1.0,
175        )
176
177        if self._unique_id == '#2':
178            self._capture_button(
179                pos=(self._width * 0.5, v + 0.1 * dist),
180                color=(0.4, 0.4, 0.6),
181                button='buttonStart',
182                texture=bui.gettexture('startButton'),
183                scale=0.8,
184            )
185
186        h_offs = self._width - 160
187
188        self._capture_button(
189            pos=(h_offs, v + 0.95 * dist),
190            color=(0.6, 0.4, 0.8),
191            button='buttonPickUp',
192            texture=bui.gettexture('buttonPickUp'),
193            scale=1.0,
194        )
195        self._capture_button(
196            pos=(h_offs - 1.2 * dist, v),
197            color=(0.7, 0.5, 0.1),
198            button='buttonPunch',
199            texture=bui.gettexture('buttonPunch'),
200            scale=1.0,
201        )
202        self._capture_button(
203            pos=(h_offs + 1.2 * dist, v),
204            color=(0.5, 0.2, 0.1),
205            button='buttonBomb',
206            texture=bui.gettexture('buttonBomb'),
207            scale=1.0,
208        )
209        self._capture_button(
210            pos=(h_offs, v - 0.95 * dist),
211            color=(0.2, 0.5, 0.2),
212            button='buttonJump',
213            texture=bui.gettexture('buttonJump'),
214            scale=1.0,
215        )
216
217        self._more_button = bui.buttonwidget(
218            parent=self._root_widget,
219            autoselect=True,
220            label='...',
221            text_scale=0.9,
222            color=(0.45, 0.4, 0.5),
223            textcolor=(0.65, 0.6, 0.7),
224            position=(self._width * 0.5 - 65, 30),
225            size=(130, 40),
226            on_activate_call=self._do_more,
227        )
228
229        if is_reset:
230            bui.containerwidget(
231                edit=self._root_widget,
232                selected_child=self._more_button,
233            )
234
235    def _pretty_button_name(self, button_name: str) -> bui.Lstr:
236        button_id = self._settings[button_name]
237        if button_id == -1:
238            return bs.Lstr(resource='configGamepadWindow.unsetText')
239        return self._input.get_button_name(button_id)
240
241    def _capture_button(
242        self,
243        pos: tuple[float, float],
244        color: tuple[float, float, float],
245        texture: bui.Texture,
246        button: str,
247        scale: float = 1.0,
248    ) -> None:
249        base_size = 79
250        btn = bui.buttonwidget(
251            parent=self._root_widget,
252            autoselect=True,
253            position=(
254                pos[0] - base_size * 0.5 * scale,
255                pos[1] - base_size * 0.5 * scale,
256            ),
257            size=(base_size * scale, base_size * scale),
258            texture=texture,
259            label='',
260            color=color,
261        )
262
263        # Do this deferred so it shows up on top of other buttons. (ew.)
264        def doit() -> None:
265            if not self._root_widget:
266                return
267            uiscale = 0.66 * scale * 2.0
268            maxwidth = 76.0 * scale
269            txt = bui.textwidget(
270                parent=self._root_widget,
271                position=(pos[0] + 0.0 * scale, pos[1] - (57.0 - 18.0) * scale),
272                color=(1, 1, 1, 0.3),
273                size=(0, 0),
274                h_align='center',
275                v_align='top',
276                scale=uiscale,
277                maxwidth=maxwidth,
278                text=self._pretty_button_name(button),
279            )
280            bui.buttonwidget(
281                edit=btn,
282                autoselect=True,
283                on_activate_call=bui.Call(
284                    AwaitKeyboardInputWindow, button, txt, self._settings
285                ),
286            )
287
288        bui.pushcall(doit)
289
290    def _cancel(self) -> None:
291        from bauiv1lib.settings.controls import ControlsSettingsWindow
292
293        # no-op if our underlying widget is dead or on its way out.
294        if not self._root_widget or self._root_widget.transitioning_out:
295            return
296
297        bui.containerwidget(edit=self._root_widget, transition='out_right')
298        assert bui.app.classic is not None
299        bui.app.ui_v1.set_main_menu_window(
300            ControlsSettingsWindow(transition='in_left').get_root_widget(),
301            from_window=self._root_widget,
302        )
303
304    def _reset(self) -> None:
305        from bauiv1lib.confirm import ConfirmWindow
306
307        assert bui.app.classic is not None
308
309        # efro note: I think it's ok to reset without a confirm here
310        # because the user can see pretty clearly what changes and can
311        # cancel out of the keyboard settings edit if they want.
312        if bool(False):
313            ConfirmWindow(
314                # TODO: Implement a translation string for this!
315                'Are you sure you want to reset your button mapping?',
316                self._do_reset,
317                width=480,
318                height=95,
319            )
320        else:
321            self._do_reset()
322
323    def _do_reset(self) -> None:
324        """Resets the input's mapping settings."""
325        self._settings = {}
326        self._get_config_mapping(default=True)
327        self._rebuild_ui(is_reset=True)
328        bui.getsound('gunCocking').play()
329
330    def _do_more(self) -> None:
331        """Show a burger menu with extra settings."""
332        # pylint: disable=cyclic-import
333        choices: list[str] = [
334            'reset',
335        ]
336        choices_display: list[bui.Lstr] = [
337            bui.Lstr(resource='settingsWindowAdvanced.resetText'),
338        ]
339
340        uiscale = bui.app.ui_v1.uiscale
341        PopupMenuWindow(
342            position=self._more_button.get_screen_space_center(),
343            scale=(
344                2.3
345                if uiscale is bui.UIScale.SMALL
346                else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23
347            ),
348            width=150,
349            choices=choices,
350            choices_display=choices_display,
351            current_choice='reset',
352            delegate=self,
353        )
354
355    def popup_menu_selected_choice(
356        self, popup_window: PopupMenuWindow, choice: str
357    ) -> None:
358        """Called when a choice is selected in the popup."""
359        del popup_window  # unused
360        if choice == 'reset':
361            self._reset()
362        else:
363            print(f'invalid choice: {choice}')
364
365    def popup_menu_closing(self, popup_window: PopupWindow) -> None:
366        """Called when the popup is closing."""
367
368    def _save(self) -> None:
369        from bauiv1lib.settings.controls import ControlsSettingsWindow
370
371        # no-op if our underlying widget is dead or on its way out.
372        if not self._root_widget or self._root_widget.transitioning_out:
373            return
374
375        assert bui.app.classic is not None
376        bui.containerwidget(edit=self._root_widget, transition='out_right')
377        bui.getsound('gunCocking').play()
378
379        # There's a chance the device disappeared; handle that gracefully.
380        if not self._input:
381            return
382
383        dst = bui.app.classic.get_input_device_config(
384            self._input, default=False
385        )
386        dst2: dict[str, Any] = dst[0][dst[1]]
387        dst2.clear()
388
389        # Store any values that aren't -1.
390        for key, val in list(self._settings.items()):
391            if val != -1:
392                dst2[key] = val
393
394        # Send this config to the master-server so we can generate
395        # more defaults in the future.
396        if bui.app.classic is not None:
397            bui.app.classic.master_server_v1_post(
398                'controllerConfig',
399                {
400                    'ua': bui.app.classic.legacy_user_agent_string,
401                    'name': self._name,
402                    'b': bui.app.env.engine_build_number,
403                    'config': dst2,
404                    'v': 2,
405                },
406            )
407        bui.app.config.apply_and_commit()
408        bui.app.ui_v1.set_main_menu_window(
409            ControlsSettingsWindow(transition='in_left').get_root_widget(),
410            from_window=self._root_widget,
411        )
412
413
414class AwaitKeyboardInputWindow(bui.Window):
415    """Window for capturing a keypress."""
416
417    def __init__(self, button: str, ui: bui.Widget, settings: dict):
418        self._capture_button = button
419        self._capture_key_ui = ui
420        self._settings = settings
421
422        width = 400
423        height = 150
424        assert bui.app.classic is not None
425        uiscale = bui.app.ui_v1.uiscale
426        super().__init__(
427            root_widget=bui.containerwidget(
428                size=(width, height),
429                transition='in_right',
430                scale=(
431                    2.0
432                    if uiscale is bui.UIScale.SMALL
433                    else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
434                ),
435            )
436        )
437        bui.textwidget(
438            parent=self._root_widget,
439            position=(0, height - 60),
440            size=(width, 25),
441            text=bui.Lstr(resource='pressAnyKeyText'),
442            h_align='center',
443            v_align='top',
444        )
445
446        self._counter = 5
447        self._count_down_text = bui.textwidget(
448            parent=self._root_widget,
449            h_align='center',
450            position=(0, height - 110),
451            size=(width, 25),
452            color=(1, 1, 1, 0.3),
453            text=str(self._counter),
454        )
455        self._decrement_timer: bui.AppTimer | None = bui.AppTimer(
456            1.0, self._decrement, repeat=True
457        )
458        bs.capture_keyboard_input(bui.WeakCall(self._button_callback))
459
460    def __del__(self) -> None:
461        bs.release_keyboard_input()
462
463    def _die(self) -> None:
464        # This strong-refs us; killing it allows us to die now.
465        self._decrement_timer = None
466        if self._root_widget:
467            bui.containerwidget(edit=self._root_widget, transition='out_left')
468
469    def _button_callback(self, event: dict[str, Any]) -> None:
470        self._settings[self._capture_button] = event['button']
471        if event['type'] == 'BUTTONDOWN':
472            bname = event['input_device'].get_button_name(event['button'])
473            bui.textwidget(edit=self._capture_key_ui, text=bname)
474            bui.getsound('gunCocking').play()
475            self._die()
476
477    def _decrement(self) -> None:
478        self._counter -= 1
479        if self._counter >= 1:
480            bui.textwidget(edit=self._count_down_text, text=str(self._counter))
481        else:
482            self._die()
class ConfigKeyboardWindow(bauiv1._uitypes.Window):
 19class ConfigKeyboardWindow(bui.Window):
 20    """Window for configuring keyboards."""
 21
 22    def __init__(self, c: bs.InputDevice, transition: str = 'in_right'):
 23        self._r = 'configKeyboardWindow'
 24        self._input = c
 25        self._name = self._input.name
 26        self._unique_id = self._input.unique_identifier
 27        dname_raw = self._name
 28        if self._unique_id != '#1':
 29            dname_raw += ' ' + self._unique_id.replace('#', 'P')
 30        self._displayname = bui.Lstr(translate=('inputDeviceNames', dname_raw))
 31        self._width = 700
 32        if self._unique_id != '#1':
 33            self._height = 480
 34        else:
 35            self._height = 375
 36        self._spacing = 40
 37        assert bui.app.classic is not None
 38        uiscale = bui.app.ui_v1.uiscale
 39        super().__init__(
 40            root_widget=bui.containerwidget(
 41                size=(self._width, self._height),
 42                scale=(
 43                    1.6
 44                    if uiscale is bui.UIScale.SMALL
 45                    else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
 46                ),
 47                stack_offset=(0, 5) if uiscale is bui.UIScale.SMALL else (0, 0),
 48                transition=transition,
 49            )
 50        )
 51
 52        self._settings: dict[str, int] = {}
 53        self._get_config_mapping()
 54
 55        self._rebuild_ui()
 56
 57    def _get_config_mapping(self, default: bool = False) -> None:
 58        for button in [
 59            'buttonJump',
 60            'buttonPunch',
 61            'buttonBomb',
 62            'buttonPickUp',
 63            'buttonStart',
 64            'buttonStart2',
 65            'buttonUp',
 66            'buttonDown',
 67            'buttonLeft',
 68            'buttonRight',
 69        ]:
 70            assert bui.app.classic is not None
 71            self._settings[button] = (
 72                bui.app.classic.get_input_device_mapped_value(
 73                    self._input, button, default
 74                )
 75            )
 76
 77    def _rebuild_ui(self, is_reset: bool = False) -> None:
 78        assert bui.app.classic is not None
 79
 80        for widget in self._root_widget.get_children():
 81            widget.delete()
 82
 83        # b_off = 0 if self._unique_id != '#1' else 9
 84        cancel_button = bui.buttonwidget(
 85            parent=self._root_widget,
 86            autoselect=True,
 87            position=(38, self._height - 85),
 88            size=(170, 60),
 89            label=bui.Lstr(resource='cancelText'),
 90            scale=0.9,
 91            on_activate_call=self._cancel,
 92        )
 93        save_button = bui.buttonwidget(
 94            parent=self._root_widget,
 95            autoselect=True,
 96            position=(self._width - 190, self._height - 85),
 97            size=(180, 60),
 98            label=bui.Lstr(resource='saveText'),
 99            scale=0.9,
100            text_scale=0.9,
101            on_activate_call=self._save,
102        )
103        bui.containerwidget(
104            edit=self._root_widget,
105            cancel_button=cancel_button,
106            start_button=save_button,
107        )
108
109        v = self._height - 74.0
110        bui.textwidget(
111            parent=self._root_widget,
112            position=(self._width * 0.5, v + 15),
113            size=(0, 0),
114            text=bui.Lstr(
115                resource=self._r + '.configuringText',
116                subs=[('${DEVICE}', self._displayname)],
117            ),
118            color=bui.app.ui_v1.title_color,
119            h_align='center',
120            v_align='center',
121            maxwidth=270,
122            scale=0.83,
123        )
124        v -= 20
125
126        if self._unique_id != '#1':
127            v -= 20
128            v -= self._spacing
129            bui.textwidget(
130                parent=self._root_widget,
131                position=(0, v + 19),
132                size=(self._width, 50),
133                text=bui.Lstr(resource=self._r + '.keyboard2NoteText'),
134                scale=0.7,
135                maxwidth=self._width * 0.75,
136                max_height=110,
137                color=bui.app.ui_v1.infotextcolor,
138                h_align='center',
139                v_align='top',
140            )
141            v -= 40
142        v -= 10
143        v -= self._spacing * 2.2
144        v += 25
145        v -= 42
146        h_offs = 160
147        dist = 70
148        d_color = (0.4, 0.4, 0.8)
149        self._capture_button(
150            pos=(h_offs, v + 0.95 * dist),
151            color=d_color,
152            button='buttonUp',
153            texture=bui.gettexture('upButton'),
154            scale=1.0,
155        )
156        self._capture_button(
157            pos=(h_offs - 1.2 * dist, v),
158            color=d_color,
159            button='buttonLeft',
160            texture=bui.gettexture('leftButton'),
161            scale=1.0,
162        )
163        self._capture_button(
164            pos=(h_offs + 1.2 * dist, v),
165            color=d_color,
166            button='buttonRight',
167            texture=bui.gettexture('rightButton'),
168            scale=1.0,
169        )
170        self._capture_button(
171            pos=(h_offs, v - 0.95 * dist),
172            color=d_color,
173            button='buttonDown',
174            texture=bui.gettexture('downButton'),
175            scale=1.0,
176        )
177
178        if self._unique_id == '#2':
179            self._capture_button(
180                pos=(self._width * 0.5, v + 0.1 * dist),
181                color=(0.4, 0.4, 0.6),
182                button='buttonStart',
183                texture=bui.gettexture('startButton'),
184                scale=0.8,
185            )
186
187        h_offs = self._width - 160
188
189        self._capture_button(
190            pos=(h_offs, v + 0.95 * dist),
191            color=(0.6, 0.4, 0.8),
192            button='buttonPickUp',
193            texture=bui.gettexture('buttonPickUp'),
194            scale=1.0,
195        )
196        self._capture_button(
197            pos=(h_offs - 1.2 * dist, v),
198            color=(0.7, 0.5, 0.1),
199            button='buttonPunch',
200            texture=bui.gettexture('buttonPunch'),
201            scale=1.0,
202        )
203        self._capture_button(
204            pos=(h_offs + 1.2 * dist, v),
205            color=(0.5, 0.2, 0.1),
206            button='buttonBomb',
207            texture=bui.gettexture('buttonBomb'),
208            scale=1.0,
209        )
210        self._capture_button(
211            pos=(h_offs, v - 0.95 * dist),
212            color=(0.2, 0.5, 0.2),
213            button='buttonJump',
214            texture=bui.gettexture('buttonJump'),
215            scale=1.0,
216        )
217
218        self._more_button = bui.buttonwidget(
219            parent=self._root_widget,
220            autoselect=True,
221            label='...',
222            text_scale=0.9,
223            color=(0.45, 0.4, 0.5),
224            textcolor=(0.65, 0.6, 0.7),
225            position=(self._width * 0.5 - 65, 30),
226            size=(130, 40),
227            on_activate_call=self._do_more,
228        )
229
230        if is_reset:
231            bui.containerwidget(
232                edit=self._root_widget,
233                selected_child=self._more_button,
234            )
235
236    def _pretty_button_name(self, button_name: str) -> bui.Lstr:
237        button_id = self._settings[button_name]
238        if button_id == -1:
239            return bs.Lstr(resource='configGamepadWindow.unsetText')
240        return self._input.get_button_name(button_id)
241
242    def _capture_button(
243        self,
244        pos: tuple[float, float],
245        color: tuple[float, float, float],
246        texture: bui.Texture,
247        button: str,
248        scale: float = 1.0,
249    ) -> None:
250        base_size = 79
251        btn = bui.buttonwidget(
252            parent=self._root_widget,
253            autoselect=True,
254            position=(
255                pos[0] - base_size * 0.5 * scale,
256                pos[1] - base_size * 0.5 * scale,
257            ),
258            size=(base_size * scale, base_size * scale),
259            texture=texture,
260            label='',
261            color=color,
262        )
263
264        # Do this deferred so it shows up on top of other buttons. (ew.)
265        def doit() -> None:
266            if not self._root_widget:
267                return
268            uiscale = 0.66 * scale * 2.0
269            maxwidth = 76.0 * scale
270            txt = bui.textwidget(
271                parent=self._root_widget,
272                position=(pos[0] + 0.0 * scale, pos[1] - (57.0 - 18.0) * scale),
273                color=(1, 1, 1, 0.3),
274                size=(0, 0),
275                h_align='center',
276                v_align='top',
277                scale=uiscale,
278                maxwidth=maxwidth,
279                text=self._pretty_button_name(button),
280            )
281            bui.buttonwidget(
282                edit=btn,
283                autoselect=True,
284                on_activate_call=bui.Call(
285                    AwaitKeyboardInputWindow, button, txt, self._settings
286                ),
287            )
288
289        bui.pushcall(doit)
290
291    def _cancel(self) -> None:
292        from bauiv1lib.settings.controls import ControlsSettingsWindow
293
294        # no-op if our underlying widget is dead or on its way out.
295        if not self._root_widget or self._root_widget.transitioning_out:
296            return
297
298        bui.containerwidget(edit=self._root_widget, transition='out_right')
299        assert bui.app.classic is not None
300        bui.app.ui_v1.set_main_menu_window(
301            ControlsSettingsWindow(transition='in_left').get_root_widget(),
302            from_window=self._root_widget,
303        )
304
305    def _reset(self) -> None:
306        from bauiv1lib.confirm import ConfirmWindow
307
308        assert bui.app.classic is not None
309
310        # efro note: I think it's ok to reset without a confirm here
311        # because the user can see pretty clearly what changes and can
312        # cancel out of the keyboard settings edit if they want.
313        if bool(False):
314            ConfirmWindow(
315                # TODO: Implement a translation string for this!
316                'Are you sure you want to reset your button mapping?',
317                self._do_reset,
318                width=480,
319                height=95,
320            )
321        else:
322            self._do_reset()
323
324    def _do_reset(self) -> None:
325        """Resets the input's mapping settings."""
326        self._settings = {}
327        self._get_config_mapping(default=True)
328        self._rebuild_ui(is_reset=True)
329        bui.getsound('gunCocking').play()
330
331    def _do_more(self) -> None:
332        """Show a burger menu with extra settings."""
333        # pylint: disable=cyclic-import
334        choices: list[str] = [
335            'reset',
336        ]
337        choices_display: list[bui.Lstr] = [
338            bui.Lstr(resource='settingsWindowAdvanced.resetText'),
339        ]
340
341        uiscale = bui.app.ui_v1.uiscale
342        PopupMenuWindow(
343            position=self._more_button.get_screen_space_center(),
344            scale=(
345                2.3
346                if uiscale is bui.UIScale.SMALL
347                else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23
348            ),
349            width=150,
350            choices=choices,
351            choices_display=choices_display,
352            current_choice='reset',
353            delegate=self,
354        )
355
356    def popup_menu_selected_choice(
357        self, popup_window: PopupMenuWindow, choice: str
358    ) -> None:
359        """Called when a choice is selected in the popup."""
360        del popup_window  # unused
361        if choice == 'reset':
362            self._reset()
363        else:
364            print(f'invalid choice: {choice}')
365
366    def popup_menu_closing(self, popup_window: PopupWindow) -> None:
367        """Called when the popup is closing."""
368
369    def _save(self) -> None:
370        from bauiv1lib.settings.controls import ControlsSettingsWindow
371
372        # no-op if our underlying widget is dead or on its way out.
373        if not self._root_widget or self._root_widget.transitioning_out:
374            return
375
376        assert bui.app.classic is not None
377        bui.containerwidget(edit=self._root_widget, transition='out_right')
378        bui.getsound('gunCocking').play()
379
380        # There's a chance the device disappeared; handle that gracefully.
381        if not self._input:
382            return
383
384        dst = bui.app.classic.get_input_device_config(
385            self._input, default=False
386        )
387        dst2: dict[str, Any] = dst[0][dst[1]]
388        dst2.clear()
389
390        # Store any values that aren't -1.
391        for key, val in list(self._settings.items()):
392            if val != -1:
393                dst2[key] = val
394
395        # Send this config to the master-server so we can generate
396        # more defaults in the future.
397        if bui.app.classic is not None:
398            bui.app.classic.master_server_v1_post(
399                'controllerConfig',
400                {
401                    'ua': bui.app.classic.legacy_user_agent_string,
402                    'name': self._name,
403                    'b': bui.app.env.engine_build_number,
404                    'config': dst2,
405                    'v': 2,
406                },
407            )
408        bui.app.config.apply_and_commit()
409        bui.app.ui_v1.set_main_menu_window(
410            ControlsSettingsWindow(transition='in_left').get_root_widget(),
411            from_window=self._root_widget,
412        )

Window for configuring keyboards.

ConfigKeyboardWindow(c: _bascenev1.InputDevice, transition: str = 'in_right')
22    def __init__(self, c: bs.InputDevice, transition: str = 'in_right'):
23        self._r = 'configKeyboardWindow'
24        self._input = c
25        self._name = self._input.name
26        self._unique_id = self._input.unique_identifier
27        dname_raw = self._name
28        if self._unique_id != '#1':
29            dname_raw += ' ' + self._unique_id.replace('#', 'P')
30        self._displayname = bui.Lstr(translate=('inputDeviceNames', dname_raw))
31        self._width = 700
32        if self._unique_id != '#1':
33            self._height = 480
34        else:
35            self._height = 375
36        self._spacing = 40
37        assert bui.app.classic is not None
38        uiscale = bui.app.ui_v1.uiscale
39        super().__init__(
40            root_widget=bui.containerwidget(
41                size=(self._width, self._height),
42                scale=(
43                    1.6
44                    if uiscale is bui.UIScale.SMALL
45                    else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
46                ),
47                stack_offset=(0, 5) if uiscale is bui.UIScale.SMALL else (0, 0),
48                transition=transition,
49            )
50        )
51
52        self._settings: dict[str, int] = {}
53        self._get_config_mapping()
54
55        self._rebuild_ui()
def popup_menu_selected_choice(self, popup_window: bauiv1lib.popup.PopupMenuWindow, choice: str) -> None:
356    def popup_menu_selected_choice(
357        self, popup_window: PopupMenuWindow, choice: str
358    ) -> None:
359        """Called when a choice is selected in the popup."""
360        del popup_window  # unused
361        if choice == 'reset':
362            self._reset()
363        else:
364            print(f'invalid choice: {choice}')

Called when a choice is selected in the popup.

def popup_menu_closing(self, popup_window: bauiv1lib.popup.PopupWindow) -> None:
366    def popup_menu_closing(self, popup_window: PopupWindow) -> None:
367        """Called when the popup is closing."""

Called when the popup is closing.

Inherited Members
bauiv1._uitypes.Window
get_root_widget
class AwaitKeyboardInputWindow(bauiv1._uitypes.Window):
415class AwaitKeyboardInputWindow(bui.Window):
416    """Window for capturing a keypress."""
417
418    def __init__(self, button: str, ui: bui.Widget, settings: dict):
419        self._capture_button = button
420        self._capture_key_ui = ui
421        self._settings = settings
422
423        width = 400
424        height = 150
425        assert bui.app.classic is not None
426        uiscale = bui.app.ui_v1.uiscale
427        super().__init__(
428            root_widget=bui.containerwidget(
429                size=(width, height),
430                transition='in_right',
431                scale=(
432                    2.0
433                    if uiscale is bui.UIScale.SMALL
434                    else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
435                ),
436            )
437        )
438        bui.textwidget(
439            parent=self._root_widget,
440            position=(0, height - 60),
441            size=(width, 25),
442            text=bui.Lstr(resource='pressAnyKeyText'),
443            h_align='center',
444            v_align='top',
445        )
446
447        self._counter = 5
448        self._count_down_text = bui.textwidget(
449            parent=self._root_widget,
450            h_align='center',
451            position=(0, height - 110),
452            size=(width, 25),
453            color=(1, 1, 1, 0.3),
454            text=str(self._counter),
455        )
456        self._decrement_timer: bui.AppTimer | None = bui.AppTimer(
457            1.0, self._decrement, repeat=True
458        )
459        bs.capture_keyboard_input(bui.WeakCall(self._button_callback))
460
461    def __del__(self) -> None:
462        bs.release_keyboard_input()
463
464    def _die(self) -> None:
465        # This strong-refs us; killing it allows us to die now.
466        self._decrement_timer = None
467        if self._root_widget:
468            bui.containerwidget(edit=self._root_widget, transition='out_left')
469
470    def _button_callback(self, event: dict[str, Any]) -> None:
471        self._settings[self._capture_button] = event['button']
472        if event['type'] == 'BUTTONDOWN':
473            bname = event['input_device'].get_button_name(event['button'])
474            bui.textwidget(edit=self._capture_key_ui, text=bname)
475            bui.getsound('gunCocking').play()
476            self._die()
477
478    def _decrement(self) -> None:
479        self._counter -= 1
480        if self._counter >= 1:
481            bui.textwidget(edit=self._count_down_text, text=str(self._counter))
482        else:
483            self._die()

Window for capturing a keypress.

AwaitKeyboardInputWindow(button: str, ui: _bauiv1.Widget, settings: dict)
418    def __init__(self, button: str, ui: bui.Widget, settings: dict):
419        self._capture_button = button
420        self._capture_key_ui = ui
421        self._settings = settings
422
423        width = 400
424        height = 150
425        assert bui.app.classic is not None
426        uiscale = bui.app.ui_v1.uiscale
427        super().__init__(
428            root_widget=bui.containerwidget(
429                size=(width, height),
430                transition='in_right',
431                scale=(
432                    2.0
433                    if uiscale is bui.UIScale.SMALL
434                    else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
435                ),
436            )
437        )
438        bui.textwidget(
439            parent=self._root_widget,
440            position=(0, height - 60),
441            size=(width, 25),
442            text=bui.Lstr(resource='pressAnyKeyText'),
443            h_align='center',
444            v_align='top',
445        )
446
447        self._counter = 5
448        self._count_down_text = bui.textwidget(
449            parent=self._root_widget,
450            h_align='center',
451            position=(0, height - 110),
452            size=(width, 25),
453            color=(1, 1, 1, 0.3),
454            text=str(self._counter),
455        )
456        self._decrement_timer: bui.AppTimer | None = bui.AppTimer(
457            1.0, self._decrement, repeat=True
458        )
459        bs.capture_keyboard_input(bui.WeakCall(self._button_callback))
Inherited Members
bauiv1._uitypes.Window
get_root_widget