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

Window for configuring keyboards.

ConfigKeyboardWindow( c: _bascenev1.InputDevice, transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
23    def __init__(
24        self,
25        c: bs.InputDevice,
26        transition: str | None = 'in_right',
27        origin_widget: bui.Widget | None = None,
28    ):
29        self._r = 'configKeyboardWindow'
30        self._input = c
31        self._name = self._input.name
32        self._unique_id = self._input.unique_identifier
33        dname_raw = self._name
34        if self._unique_id != '#1':
35            dname_raw += ' ' + self._unique_id.replace('#', 'P')
36        self._displayname = bui.Lstr(translate=('inputDeviceNames', dname_raw))
37        self._width = 700
38        if self._unique_id != '#1':
39            self._height = 480
40        else:
41            self._height = 375
42        self._spacing = 40
43        assert bui.app.classic is not None
44        uiscale = bui.app.ui_v1.uiscale
45        super().__init__(
46            root_widget=bui.containerwidget(
47                size=(self._width, self._height),
48                scale=(
49                    1.4
50                    if uiscale is bui.UIScale.SMALL
51                    else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
52                ),
53                stack_offset=(0, 5) if uiscale is bui.UIScale.SMALL else (0, 0),
54                transition=transition,
55            ),
56            transition=transition,
57            origin_widget=origin_widget,
58        )
59
60        self._settings: dict[str, int] = {}
61        self._get_config_mapping()
62
63        self._rebuild_ui()

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:
65    @override
66    def get_main_window_state(self) -> bui.MainWindowState:
67        # Support recreating our window for back/refresh purposes.
68        cls = type(self)
69
70        # Pull things from self here; if we do it within the lambda
71        # we'll keep self alive which is bad.
72        inputdevice = self._input
73
74        return bui.BasicMainWindowState(
75            create_call=lambda transition, origin_widget: cls(
76                transition=transition,
77                origin_widget=origin_widget,
78                c=inputdevice,
79            )
80        )

Return a WindowState to recreate this window, if supported.

def popup_menu_selected_choice(self, popup_window: bauiv1lib.popup.PopupMenuWindow, choice: str) -> None:
367    def popup_menu_selected_choice(
368        self, popup_window: PopupMenuWindow, choice: str
369    ) -> None:
370        """Called when a choice is selected in the popup."""
371        del popup_window  # unused
372        if choice == 'reset':
373            self._reset()
374        else:
375            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:
377    def popup_menu_closing(self, popup_window: PopupWindow) -> None:
378        """Called when the popup is closing."""

Called when the popup is closing.

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

Window for capturing a keypress.

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