bauiv1lib.settings.controls

Provides a top level control settings window.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides a top level control settings window."""
  4
  5from __future__ import annotations
  6
  7from typing import override
  8
  9from bauiv1lib.popup import PopupMenu
 10import bascenev1 as bs
 11import bauiv1 as bui
 12
 13
 14class ControlsSettingsWindow(bui.MainWindow):
 15    """Top level control settings window."""
 16
 17    def __init__(
 18        self,
 19        transition: str | None = 'in_right',
 20        origin_widget: bui.Widget | None = None,
 21    ):
 22        # FIXME: should tidy up here.
 23        # pylint: disable=too-many-statements
 24        # pylint: disable=too-many-branches
 25        # pylint: disable=too-many-locals
 26        # pylint: disable=cyclic-import
 27
 28        self._have_selected_child = False
 29
 30        self._r = 'configControllersWindow'
 31        uiscale = bui.app.ui_v1.uiscale
 32        app = bui.app
 33        assert app.classic is not None
 34
 35        spacing = 50.0
 36        button_width = 350.0
 37        width = 800.0 if uiscale is bui.UIScale.SMALL else 460.0
 38        height = 300 if uiscale is bui.UIScale.SMALL else 130.0
 39
 40        yoffs = -60 if uiscale is bui.UIScale.SMALL else 0
 41        space_height = spacing * 0.3
 42
 43        # FIXME: should create vis settings under platform or
 44        # app-adapter to determine whether to show this stuff; not hard
 45        # code it.
 46
 47        show_gamepads = False
 48        platform = app.classic.platform
 49        subplatform = app.classic.subplatform
 50        non_vr_windows = platform == 'windows' and (
 51            subplatform != 'oculus' or not app.env.vr
 52        )
 53        if platform in ('linux', 'android', 'mac') or non_vr_windows:
 54            show_gamepads = True
 55            height += spacing
 56
 57        show_touch = False
 58        if bs.have_touchscreen_input():
 59            show_touch = True
 60            height += spacing
 61
 62        show_space_1 = False
 63        if show_gamepads or show_touch:
 64            show_space_1 = True
 65            height += space_height
 66
 67        show_keyboard = False
 68        if bs.getinputdevice('Keyboard', '#1', doraise=False) is not None:
 69            show_keyboard = True
 70            height += spacing
 71        show_keyboard_p2 = False if app.env.vr else show_keyboard
 72        if show_keyboard_p2:
 73            height += spacing
 74
 75        show_space_2 = False
 76        if show_keyboard:
 77            show_space_2 = True
 78            height += space_height
 79
 80        if bool(True):
 81            show_remote = True
 82            height += spacing
 83        else:
 84            show_remote = False
 85
 86        # On windows (outside of oculus/vr), show an option to disable xinput.
 87        show_xinput_toggle = False
 88        if platform == 'windows' and not app.env.vr:
 89            show_xinput_toggle = True
 90
 91        # On mac builds, show an option to switch between generic and
 92        # made-for-iOS/Mac systems
 93        # (we can run into problems where devices register as one of each
 94        # type otherwise)..
 95        # UPDATE: We always use the apple system these days (which should
 96        # support older controllers). So no need for a switch.
 97        show_mac_controller_subsystem = False
 98        # if platform == 'mac' and bui.is_xcode_build():
 99        #     show_mac_controller_subsystem = True
100
101        if show_mac_controller_subsystem:
102            height += spacing * 1.5
103
104        if show_xinput_toggle:
105            height += spacing
106
107        assert bui.app.classic is not None
108        smallscale = 1.7 if show_keyboard else 2.2
109        super().__init__(
110            root_widget=bui.containerwidget(
111                size=(width, height),
112                stack_offset=(
113                    (0, -10) if uiscale is bui.UIScale.SMALL else (0, 0)
114                ),
115                scale=(
116                    smallscale
117                    if uiscale is bui.UIScale.SMALL
118                    else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
119                ),
120                toolbar_visibility=(
121                    'menu_minimal'
122                    if uiscale is bui.UIScale.SMALL
123                    else 'menu_full'
124                ),
125            ),
126            transition=transition,
127            origin_widget=origin_widget,
128        )
129
130        self._back_button: bui.Widget | None
131        if uiscale is bui.UIScale.SMALL:
132            bui.containerwidget(
133                edit=self._root_widget, on_cancel_call=self.main_window_back
134            )
135            self._back_button = None
136        else:
137            self._back_button = btn = bui.buttonwidget(
138                parent=self._root_widget,
139                position=(35, height - 60),
140                size=(140, 65),
141                scale=0.8,
142                text_scale=1.2,
143                autoselect=True,
144                label=bui.Lstr(resource='backText'),
145                button_type='back',
146                on_activate_call=self.main_window_back,
147            )
148            bui.containerwidget(edit=self._root_widget, cancel_button=btn)
149            bui.buttonwidget(
150                edit=btn,
151                button_type='backSmall',
152                size=(60, 60),
153                label=bui.charstr(bui.SpecialChar.BACK),
154            )
155
156        # We need these vars to exist even if the buttons don't.
157        self._gamepads_button: bui.Widget | None = None
158        self._touch_button: bui.Widget | None = None
159        self._keyboard_button: bui.Widget | None = None
160        self._keyboard_2_button: bui.Widget | None = None
161        self._idevices_button: bui.Widget | None = None
162
163        bui.textwidget(
164            parent=self._root_widget,
165            position=(0, height - 49 + yoffs),
166            size=(width, 25),
167            text=bui.Lstr(resource=f'{self._r}.titleText'),
168            color=bui.app.ui_v1.title_color,
169            h_align='center',
170            v_align='top',
171        )
172
173        v = height - 75 + yoffs
174        v -= spacing
175
176        if show_touch:
177            self._touch_button = btn = bui.buttonwidget(
178                parent=self._root_widget,
179                position=((width - button_width) / 2, v),
180                size=(button_width, 43),
181                autoselect=True,
182                label=bui.Lstr(resource=f'{self._r}.configureTouchText'),
183                on_activate_call=self._do_touchscreen,
184            )
185            bui.widget(
186                edit=btn,
187                right_widget=bui.get_special_widget('squad_button'),
188            )
189            if not self._have_selected_child:
190                bui.containerwidget(
191                    edit=self._root_widget, selected_child=self._touch_button
192                )
193                if self._back_button is not None:
194                    bui.widget(
195                        edit=self._back_button, down_widget=self._touch_button
196                    )
197                self._have_selected_child = True
198            v -= spacing
199
200        if show_gamepads:
201            self._gamepads_button = btn = bui.buttonwidget(
202                parent=self._root_widget,
203                position=((width - button_width) / 2 - 7, v),
204                size=(button_width, 43),
205                autoselect=True,
206                label=bui.Lstr(resource=f'{self._r}.configureControllersText'),
207                on_activate_call=self._do_gamepads,
208            )
209            bui.widget(
210                edit=btn,
211                right_widget=bui.get_special_widget('squad_button'),
212            )
213            if not self._have_selected_child:
214                bui.containerwidget(
215                    edit=self._root_widget, selected_child=self._gamepads_button
216                )
217                if self._back_button is not None:
218                    bui.widget(
219                        edit=self._back_button,
220                        down_widget=self._gamepads_button,
221                    )
222                self._have_selected_child = True
223            v -= spacing
224        else:
225            self._gamepads_button = None
226
227        if show_space_1:
228            v -= space_height
229
230        if show_keyboard:
231            self._keyboard_button = btn = bui.buttonwidget(
232                parent=self._root_widget,
233                position=((width - button_width) / 2 - 5, v),
234                size=(button_width, 43),
235                autoselect=True,
236                label=bui.Lstr(resource=f'{self._r}.configureKeyboardText'),
237                on_activate_call=self._config_keyboard,
238            )
239            bui.widget(
240                edit=self._keyboard_button, left_widget=self._keyboard_button
241            )
242            bui.widget(
243                edit=btn,
244                right_widget=bui.get_special_widget('squad_button'),
245            )
246            if not self._have_selected_child:
247                bui.containerwidget(
248                    edit=self._root_widget, selected_child=self._keyboard_button
249                )
250                if self._back_button is not None:
251                    bui.widget(
252                        edit=self._back_button,
253                        down_widget=self._keyboard_button,
254                    )
255                self._have_selected_child = True
256            v -= spacing
257        if show_keyboard_p2:
258            self._keyboard_2_button = bui.buttonwidget(
259                parent=self._root_widget,
260                position=((width - button_width) / 2 - 3, v),
261                size=(button_width, 43),
262                autoselect=True,
263                label=bui.Lstr(resource=f'{self._r}.configureKeyboard2Text'),
264                on_activate_call=self._config_keyboard2,
265            )
266            v -= spacing
267            bui.widget(
268                edit=self._keyboard_2_button,
269                left_widget=self._keyboard_2_button,
270            )
271        if show_space_2:
272            v -= space_height
273        if show_remote:
274            self._idevices_button = btn = bui.buttonwidget(
275                parent=self._root_widget,
276                position=((width - button_width) / 2 - 5, v),
277                size=(button_width, 43),
278                autoselect=True,
279                label=bui.Lstr(resource=f'{self._r}.configureMobileText'),
280                on_activate_call=self._do_mobile_devices,
281            )
282            bui.widget(
283                edit=self._idevices_button, left_widget=self._idevices_button
284            )
285            bui.widget(
286                edit=btn,
287                right_widget=bui.get_special_widget('squad_button'),
288            )
289            if not self._have_selected_child:
290                bui.containerwidget(
291                    edit=self._root_widget, selected_child=self._idevices_button
292                )
293                if self._back_button is not None:
294                    bui.widget(
295                        edit=self._back_button,
296                        down_widget=self._idevices_button,
297                    )
298                self._have_selected_child = True
299            v -= spacing
300
301        if show_xinput_toggle:
302
303            def do_toggle(value: bool) -> None:
304                bui.screenmessage(
305                    bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
306                    color=(1, 1, 0),
307                )
308                bui.getsound('gunCocking').play()
309                bui.set_low_level_config_value('enablexinput', not value)
310
311            xinput_checkbox = bui.checkboxwidget(
312                parent=self._root_widget,
313                position=(100, v + 3),
314                size=(120, 30),
315                value=(not bui.get_low_level_config_value('enablexinput', 1)),
316                maxwidth=200,
317                on_value_change_call=do_toggle,
318                text=bui.Lstr(resource='disableXInputText'),
319                autoselect=True,
320            )
321            bui.textwidget(
322                parent=self._root_widget,
323                position=(width * 0.5, v - 5),
324                size=(0, 0),
325                text=bui.Lstr(resource='disableXInputDescriptionText'),
326                scale=0.5,
327                h_align='center',
328                v_align='center',
329                color=bui.app.ui_v1.infotextcolor,
330                maxwidth=width * 0.8,
331            )
332            bui.widget(
333                edit=xinput_checkbox,
334                left_widget=xinput_checkbox,
335                right_widget=xinput_checkbox,
336            )
337            v -= spacing
338
339        if show_mac_controller_subsystem:
340            PopupMenu(
341                parent=self._root_widget,
342                position=(260, v - 10),
343                width=160,
344                button_size=(150, 50),
345                scale=1.5,
346                choices=['Classic', 'MFi', 'Both'],
347                choices_display=[
348                    bui.Lstr(resource='macControllerSubsystemClassicText'),
349                    bui.Lstr(resource='macControllerSubsystemMFiText'),
350                    bui.Lstr(resource='macControllerSubsystemBothText'),
351                ],
352                current_choice=bui.app.config.resolve(
353                    'Mac Controller Subsystem'
354                ),
355                on_value_change_call=self._set_mac_controller_subsystem,
356            )
357            bui.textwidget(
358                parent=self._root_widget,
359                position=(245, v + 13),
360                size=(0, 0),
361                text=bui.Lstr(resource='macControllerSubsystemTitleText'),
362                scale=1.0,
363                h_align='right',
364                v_align='center',
365                color=bui.app.ui_v1.infotextcolor,
366                maxwidth=180,
367            )
368            bui.textwidget(
369                parent=self._root_widget,
370                position=(width * 0.5, v - 20),
371                size=(0, 0),
372                text=bui.Lstr(resource='macControllerSubsystemDescriptionText'),
373                scale=0.5,
374                h_align='center',
375                v_align='center',
376                color=bui.app.ui_v1.infotextcolor,
377                maxwidth=width * 0.8,
378            )
379            v -= spacing * 1.5
380
381        self._restore_state()
382
383    @override
384    def get_main_window_state(self) -> bui.MainWindowState:
385        # Support recreating our window for back/refresh purposes.
386        cls = type(self)
387        return bui.BasicMainWindowState(
388            create_call=lambda transition, origin_widget: cls(
389                transition=transition, origin_widget=origin_widget
390            )
391        )
392
393    @override
394    def on_main_window_close(self) -> None:
395        self._save_state()
396
397    def _set_mac_controller_subsystem(self, val: str) -> None:
398        cfg = bui.app.config
399        cfg['Mac Controller Subsystem'] = val
400        cfg.apply_and_commit()
401
402    def _config_keyboard(self) -> None:
403        # pylint: disable=cyclic-import
404        from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
405
406        # no-op if our underlying widget is dead or on its way out.
407        if not self._root_widget or self._root_widget.transitioning_out:
408            return
409
410        self._save_state()
411        bui.containerwidget(edit=self._root_widget, transition='out_left')
412        assert bui.app.classic is not None
413        bui.app.ui_v1.set_main_window(
414            ConfigKeyboardWindow(bs.getinputdevice('Keyboard', '#1')),
415            from_window=self,
416        )
417
418    def _config_keyboard2(self) -> None:
419        # pylint: disable=cyclic-import
420        from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
421
422        # no-op if our underlying widget is dead or on its way out.
423        if not self._root_widget or self._root_widget.transitioning_out:
424            return
425
426        self._save_state()
427        bui.containerwidget(edit=self._root_widget, transition='out_left')
428        assert bui.app.classic is not None
429        bui.app.ui_v1.set_main_window(
430            ConfigKeyboardWindow(bs.getinputdevice('Keyboard', '#2')),
431            from_window=self,
432        )
433
434    def _do_mobile_devices(self) -> None:
435        # pylint: disable=cyclic-import
436        from bauiv1lib.settings.remoteapp import RemoteAppSettingsWindow
437
438        # no-op if our underlying widget is dead or on its way out.
439        if not self._root_widget or self._root_widget.transitioning_out:
440            return
441
442        self._save_state()
443        bui.containerwidget(edit=self._root_widget, transition='out_left')
444        assert bui.app.classic is not None
445        bui.app.ui_v1.set_main_window(
446            RemoteAppSettingsWindow(), from_window=self
447        )
448
449    def _do_gamepads(self) -> None:
450        # pylint: disable=cyclic-import
451        from bauiv1lib.settings.gamepadselect import GamepadSelectWindow
452
453        # no-op if our underlying widget is dead or on its way out.
454        if not self._root_widget or self._root_widget.transitioning_out:
455            return
456
457        self._save_state()
458        bui.containerwidget(edit=self._root_widget, transition='out_left')
459        assert bui.app.classic is not None
460        bui.app.ui_v1.set_main_window(GamepadSelectWindow(), from_window=self)
461
462    def _do_touchscreen(self) -> None:
463        # pylint: disable=cyclic-import
464        from bauiv1lib.settings.touchscreen import TouchscreenSettingsWindow
465
466        # no-op if our underlying widget is dead or on its way out.
467        if not self._root_widget or self._root_widget.transitioning_out:
468            return
469
470        self._save_state()
471        bui.containerwidget(edit=self._root_widget, transition='out_left')
472        assert bui.app.classic is not None
473        bui.app.ui_v1.set_main_window(
474            TouchscreenSettingsWindow(), from_window=self
475        )
476
477    def _save_state(self) -> None:
478        sel = self._root_widget.get_selected_child()
479        if sel == self._gamepads_button:
480            sel_name = 'GamePads'
481        elif sel == self._touch_button:
482            sel_name = 'Touch'
483        elif sel == self._keyboard_button:
484            sel_name = 'Keyboard'
485        elif sel == self._keyboard_2_button:
486            sel_name = 'Keyboard2'
487        elif sel == self._idevices_button:
488            sel_name = 'iDevices'
489        else:
490            sel_name = 'Back'
491        assert bui.app.classic is not None
492        bui.app.ui_v1.window_states[type(self)] = sel_name
493
494    def _restore_state(self) -> None:
495        assert bui.app.classic is not None
496        sel_name = bui.app.ui_v1.window_states.get(type(self))
497        if sel_name == 'GamePads':
498            sel = self._gamepads_button
499        elif sel_name == 'Touch':
500            sel = self._touch_button
501        elif sel_name == 'Keyboard':
502            sel = self._keyboard_button
503        elif sel_name == 'Keyboard2':
504            sel = self._keyboard_2_button
505        elif sel_name == 'iDevices':
506            sel = self._idevices_button
507        elif sel_name == 'Back':
508            sel = self._back_button
509        else:
510            sel = (
511                self._gamepads_button
512                if self._gamepads_button is not None
513                else self._back_button
514            )
515        bui.containerwidget(edit=self._root_widget, selected_child=sel)
516
517    # def _back(self) -> None:
518    #     # pylint: disable=cyclic-import
519    #     from bauiv1lib.settings.allsettings import AllSettingsWindow
520
521    #     # no-op if our underlying widget is dead or on its way out.
522    #     if not self._root_widget or self._root_widget.transitioning_out:
523    #         return
524
525    #     self._save_state()
526    #     bui.containerwidget(
527    #         edit=self._root_widget, transition=self._transition_out
528    #     )
529    #     assert bui.app.classic is not None
530    #     bui.app.ui_v1.set_main_window(
531    #         AllSettingsWindow(transition='in_left'),
532    #         from_window=self,
533    #         is_back=True,
534    #     )
class ControlsSettingsWindow(bauiv1._uitypes.MainWindow):
 15class ControlsSettingsWindow(bui.MainWindow):
 16    """Top level control settings window."""
 17
 18    def __init__(
 19        self,
 20        transition: str | None = 'in_right',
 21        origin_widget: bui.Widget | None = None,
 22    ):
 23        # FIXME: should tidy up here.
 24        # pylint: disable=too-many-statements
 25        # pylint: disable=too-many-branches
 26        # pylint: disable=too-many-locals
 27        # pylint: disable=cyclic-import
 28
 29        self._have_selected_child = False
 30
 31        self._r = 'configControllersWindow'
 32        uiscale = bui.app.ui_v1.uiscale
 33        app = bui.app
 34        assert app.classic is not None
 35
 36        spacing = 50.0
 37        button_width = 350.0
 38        width = 800.0 if uiscale is bui.UIScale.SMALL else 460.0
 39        height = 300 if uiscale is bui.UIScale.SMALL else 130.0
 40
 41        yoffs = -60 if uiscale is bui.UIScale.SMALL else 0
 42        space_height = spacing * 0.3
 43
 44        # FIXME: should create vis settings under platform or
 45        # app-adapter to determine whether to show this stuff; not hard
 46        # code it.
 47
 48        show_gamepads = False
 49        platform = app.classic.platform
 50        subplatform = app.classic.subplatform
 51        non_vr_windows = platform == 'windows' and (
 52            subplatform != 'oculus' or not app.env.vr
 53        )
 54        if platform in ('linux', 'android', 'mac') or non_vr_windows:
 55            show_gamepads = True
 56            height += spacing
 57
 58        show_touch = False
 59        if bs.have_touchscreen_input():
 60            show_touch = True
 61            height += spacing
 62
 63        show_space_1 = False
 64        if show_gamepads or show_touch:
 65            show_space_1 = True
 66            height += space_height
 67
 68        show_keyboard = False
 69        if bs.getinputdevice('Keyboard', '#1', doraise=False) is not None:
 70            show_keyboard = True
 71            height += spacing
 72        show_keyboard_p2 = False if app.env.vr else show_keyboard
 73        if show_keyboard_p2:
 74            height += spacing
 75
 76        show_space_2 = False
 77        if show_keyboard:
 78            show_space_2 = True
 79            height += space_height
 80
 81        if bool(True):
 82            show_remote = True
 83            height += spacing
 84        else:
 85            show_remote = False
 86
 87        # On windows (outside of oculus/vr), show an option to disable xinput.
 88        show_xinput_toggle = False
 89        if platform == 'windows' and not app.env.vr:
 90            show_xinput_toggle = True
 91
 92        # On mac builds, show an option to switch between generic and
 93        # made-for-iOS/Mac systems
 94        # (we can run into problems where devices register as one of each
 95        # type otherwise)..
 96        # UPDATE: We always use the apple system these days (which should
 97        # support older controllers). So no need for a switch.
 98        show_mac_controller_subsystem = False
 99        # if platform == 'mac' and bui.is_xcode_build():
100        #     show_mac_controller_subsystem = True
101
102        if show_mac_controller_subsystem:
103            height += spacing * 1.5
104
105        if show_xinput_toggle:
106            height += spacing
107
108        assert bui.app.classic is not None
109        smallscale = 1.7 if show_keyboard else 2.2
110        super().__init__(
111            root_widget=bui.containerwidget(
112                size=(width, height),
113                stack_offset=(
114                    (0, -10) if uiscale is bui.UIScale.SMALL else (0, 0)
115                ),
116                scale=(
117                    smallscale
118                    if uiscale is bui.UIScale.SMALL
119                    else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
120                ),
121                toolbar_visibility=(
122                    'menu_minimal'
123                    if uiscale is bui.UIScale.SMALL
124                    else 'menu_full'
125                ),
126            ),
127            transition=transition,
128            origin_widget=origin_widget,
129        )
130
131        self._back_button: bui.Widget | None
132        if uiscale is bui.UIScale.SMALL:
133            bui.containerwidget(
134                edit=self._root_widget, on_cancel_call=self.main_window_back
135            )
136            self._back_button = None
137        else:
138            self._back_button = btn = bui.buttonwidget(
139                parent=self._root_widget,
140                position=(35, height - 60),
141                size=(140, 65),
142                scale=0.8,
143                text_scale=1.2,
144                autoselect=True,
145                label=bui.Lstr(resource='backText'),
146                button_type='back',
147                on_activate_call=self.main_window_back,
148            )
149            bui.containerwidget(edit=self._root_widget, cancel_button=btn)
150            bui.buttonwidget(
151                edit=btn,
152                button_type='backSmall',
153                size=(60, 60),
154                label=bui.charstr(bui.SpecialChar.BACK),
155            )
156
157        # We need these vars to exist even if the buttons don't.
158        self._gamepads_button: bui.Widget | None = None
159        self._touch_button: bui.Widget | None = None
160        self._keyboard_button: bui.Widget | None = None
161        self._keyboard_2_button: bui.Widget | None = None
162        self._idevices_button: bui.Widget | None = None
163
164        bui.textwidget(
165            parent=self._root_widget,
166            position=(0, height - 49 + yoffs),
167            size=(width, 25),
168            text=bui.Lstr(resource=f'{self._r}.titleText'),
169            color=bui.app.ui_v1.title_color,
170            h_align='center',
171            v_align='top',
172        )
173
174        v = height - 75 + yoffs
175        v -= spacing
176
177        if show_touch:
178            self._touch_button = btn = bui.buttonwidget(
179                parent=self._root_widget,
180                position=((width - button_width) / 2, v),
181                size=(button_width, 43),
182                autoselect=True,
183                label=bui.Lstr(resource=f'{self._r}.configureTouchText'),
184                on_activate_call=self._do_touchscreen,
185            )
186            bui.widget(
187                edit=btn,
188                right_widget=bui.get_special_widget('squad_button'),
189            )
190            if not self._have_selected_child:
191                bui.containerwidget(
192                    edit=self._root_widget, selected_child=self._touch_button
193                )
194                if self._back_button is not None:
195                    bui.widget(
196                        edit=self._back_button, down_widget=self._touch_button
197                    )
198                self._have_selected_child = True
199            v -= spacing
200
201        if show_gamepads:
202            self._gamepads_button = btn = bui.buttonwidget(
203                parent=self._root_widget,
204                position=((width - button_width) / 2 - 7, v),
205                size=(button_width, 43),
206                autoselect=True,
207                label=bui.Lstr(resource=f'{self._r}.configureControllersText'),
208                on_activate_call=self._do_gamepads,
209            )
210            bui.widget(
211                edit=btn,
212                right_widget=bui.get_special_widget('squad_button'),
213            )
214            if not self._have_selected_child:
215                bui.containerwidget(
216                    edit=self._root_widget, selected_child=self._gamepads_button
217                )
218                if self._back_button is not None:
219                    bui.widget(
220                        edit=self._back_button,
221                        down_widget=self._gamepads_button,
222                    )
223                self._have_selected_child = True
224            v -= spacing
225        else:
226            self._gamepads_button = None
227
228        if show_space_1:
229            v -= space_height
230
231        if show_keyboard:
232            self._keyboard_button = btn = bui.buttonwidget(
233                parent=self._root_widget,
234                position=((width - button_width) / 2 - 5, v),
235                size=(button_width, 43),
236                autoselect=True,
237                label=bui.Lstr(resource=f'{self._r}.configureKeyboardText'),
238                on_activate_call=self._config_keyboard,
239            )
240            bui.widget(
241                edit=self._keyboard_button, left_widget=self._keyboard_button
242            )
243            bui.widget(
244                edit=btn,
245                right_widget=bui.get_special_widget('squad_button'),
246            )
247            if not self._have_selected_child:
248                bui.containerwidget(
249                    edit=self._root_widget, selected_child=self._keyboard_button
250                )
251                if self._back_button is not None:
252                    bui.widget(
253                        edit=self._back_button,
254                        down_widget=self._keyboard_button,
255                    )
256                self._have_selected_child = True
257            v -= spacing
258        if show_keyboard_p2:
259            self._keyboard_2_button = bui.buttonwidget(
260                parent=self._root_widget,
261                position=((width - button_width) / 2 - 3, v),
262                size=(button_width, 43),
263                autoselect=True,
264                label=bui.Lstr(resource=f'{self._r}.configureKeyboard2Text'),
265                on_activate_call=self._config_keyboard2,
266            )
267            v -= spacing
268            bui.widget(
269                edit=self._keyboard_2_button,
270                left_widget=self._keyboard_2_button,
271            )
272        if show_space_2:
273            v -= space_height
274        if show_remote:
275            self._idevices_button = btn = bui.buttonwidget(
276                parent=self._root_widget,
277                position=((width - button_width) / 2 - 5, v),
278                size=(button_width, 43),
279                autoselect=True,
280                label=bui.Lstr(resource=f'{self._r}.configureMobileText'),
281                on_activate_call=self._do_mobile_devices,
282            )
283            bui.widget(
284                edit=self._idevices_button, left_widget=self._idevices_button
285            )
286            bui.widget(
287                edit=btn,
288                right_widget=bui.get_special_widget('squad_button'),
289            )
290            if not self._have_selected_child:
291                bui.containerwidget(
292                    edit=self._root_widget, selected_child=self._idevices_button
293                )
294                if self._back_button is not None:
295                    bui.widget(
296                        edit=self._back_button,
297                        down_widget=self._idevices_button,
298                    )
299                self._have_selected_child = True
300            v -= spacing
301
302        if show_xinput_toggle:
303
304            def do_toggle(value: bool) -> None:
305                bui.screenmessage(
306                    bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
307                    color=(1, 1, 0),
308                )
309                bui.getsound('gunCocking').play()
310                bui.set_low_level_config_value('enablexinput', not value)
311
312            xinput_checkbox = bui.checkboxwidget(
313                parent=self._root_widget,
314                position=(100, v + 3),
315                size=(120, 30),
316                value=(not bui.get_low_level_config_value('enablexinput', 1)),
317                maxwidth=200,
318                on_value_change_call=do_toggle,
319                text=bui.Lstr(resource='disableXInputText'),
320                autoselect=True,
321            )
322            bui.textwidget(
323                parent=self._root_widget,
324                position=(width * 0.5, v - 5),
325                size=(0, 0),
326                text=bui.Lstr(resource='disableXInputDescriptionText'),
327                scale=0.5,
328                h_align='center',
329                v_align='center',
330                color=bui.app.ui_v1.infotextcolor,
331                maxwidth=width * 0.8,
332            )
333            bui.widget(
334                edit=xinput_checkbox,
335                left_widget=xinput_checkbox,
336                right_widget=xinput_checkbox,
337            )
338            v -= spacing
339
340        if show_mac_controller_subsystem:
341            PopupMenu(
342                parent=self._root_widget,
343                position=(260, v - 10),
344                width=160,
345                button_size=(150, 50),
346                scale=1.5,
347                choices=['Classic', 'MFi', 'Both'],
348                choices_display=[
349                    bui.Lstr(resource='macControllerSubsystemClassicText'),
350                    bui.Lstr(resource='macControllerSubsystemMFiText'),
351                    bui.Lstr(resource='macControllerSubsystemBothText'),
352                ],
353                current_choice=bui.app.config.resolve(
354                    'Mac Controller Subsystem'
355                ),
356                on_value_change_call=self._set_mac_controller_subsystem,
357            )
358            bui.textwidget(
359                parent=self._root_widget,
360                position=(245, v + 13),
361                size=(0, 0),
362                text=bui.Lstr(resource='macControllerSubsystemTitleText'),
363                scale=1.0,
364                h_align='right',
365                v_align='center',
366                color=bui.app.ui_v1.infotextcolor,
367                maxwidth=180,
368            )
369            bui.textwidget(
370                parent=self._root_widget,
371                position=(width * 0.5, v - 20),
372                size=(0, 0),
373                text=bui.Lstr(resource='macControllerSubsystemDescriptionText'),
374                scale=0.5,
375                h_align='center',
376                v_align='center',
377                color=bui.app.ui_v1.infotextcolor,
378                maxwidth=width * 0.8,
379            )
380            v -= spacing * 1.5
381
382        self._restore_state()
383
384    @override
385    def get_main_window_state(self) -> bui.MainWindowState:
386        # Support recreating our window for back/refresh purposes.
387        cls = type(self)
388        return bui.BasicMainWindowState(
389            create_call=lambda transition, origin_widget: cls(
390                transition=transition, origin_widget=origin_widget
391            )
392        )
393
394    @override
395    def on_main_window_close(self) -> None:
396        self._save_state()
397
398    def _set_mac_controller_subsystem(self, val: str) -> None:
399        cfg = bui.app.config
400        cfg['Mac Controller Subsystem'] = val
401        cfg.apply_and_commit()
402
403    def _config_keyboard(self) -> None:
404        # pylint: disable=cyclic-import
405        from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
406
407        # no-op if our underlying widget is dead or on its way out.
408        if not self._root_widget or self._root_widget.transitioning_out:
409            return
410
411        self._save_state()
412        bui.containerwidget(edit=self._root_widget, transition='out_left')
413        assert bui.app.classic is not None
414        bui.app.ui_v1.set_main_window(
415            ConfigKeyboardWindow(bs.getinputdevice('Keyboard', '#1')),
416            from_window=self,
417        )
418
419    def _config_keyboard2(self) -> None:
420        # pylint: disable=cyclic-import
421        from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
422
423        # no-op if our underlying widget is dead or on its way out.
424        if not self._root_widget or self._root_widget.transitioning_out:
425            return
426
427        self._save_state()
428        bui.containerwidget(edit=self._root_widget, transition='out_left')
429        assert bui.app.classic is not None
430        bui.app.ui_v1.set_main_window(
431            ConfigKeyboardWindow(bs.getinputdevice('Keyboard', '#2')),
432            from_window=self,
433        )
434
435    def _do_mobile_devices(self) -> None:
436        # pylint: disable=cyclic-import
437        from bauiv1lib.settings.remoteapp import RemoteAppSettingsWindow
438
439        # no-op if our underlying widget is dead or on its way out.
440        if not self._root_widget or self._root_widget.transitioning_out:
441            return
442
443        self._save_state()
444        bui.containerwidget(edit=self._root_widget, transition='out_left')
445        assert bui.app.classic is not None
446        bui.app.ui_v1.set_main_window(
447            RemoteAppSettingsWindow(), from_window=self
448        )
449
450    def _do_gamepads(self) -> None:
451        # pylint: disable=cyclic-import
452        from bauiv1lib.settings.gamepadselect import GamepadSelectWindow
453
454        # no-op if our underlying widget is dead or on its way out.
455        if not self._root_widget or self._root_widget.transitioning_out:
456            return
457
458        self._save_state()
459        bui.containerwidget(edit=self._root_widget, transition='out_left')
460        assert bui.app.classic is not None
461        bui.app.ui_v1.set_main_window(GamepadSelectWindow(), from_window=self)
462
463    def _do_touchscreen(self) -> None:
464        # pylint: disable=cyclic-import
465        from bauiv1lib.settings.touchscreen import TouchscreenSettingsWindow
466
467        # no-op if our underlying widget is dead or on its way out.
468        if not self._root_widget or self._root_widget.transitioning_out:
469            return
470
471        self._save_state()
472        bui.containerwidget(edit=self._root_widget, transition='out_left')
473        assert bui.app.classic is not None
474        bui.app.ui_v1.set_main_window(
475            TouchscreenSettingsWindow(), from_window=self
476        )
477
478    def _save_state(self) -> None:
479        sel = self._root_widget.get_selected_child()
480        if sel == self._gamepads_button:
481            sel_name = 'GamePads'
482        elif sel == self._touch_button:
483            sel_name = 'Touch'
484        elif sel == self._keyboard_button:
485            sel_name = 'Keyboard'
486        elif sel == self._keyboard_2_button:
487            sel_name = 'Keyboard2'
488        elif sel == self._idevices_button:
489            sel_name = 'iDevices'
490        else:
491            sel_name = 'Back'
492        assert bui.app.classic is not None
493        bui.app.ui_v1.window_states[type(self)] = sel_name
494
495    def _restore_state(self) -> None:
496        assert bui.app.classic is not None
497        sel_name = bui.app.ui_v1.window_states.get(type(self))
498        if sel_name == 'GamePads':
499            sel = self._gamepads_button
500        elif sel_name == 'Touch':
501            sel = self._touch_button
502        elif sel_name == 'Keyboard':
503            sel = self._keyboard_button
504        elif sel_name == 'Keyboard2':
505            sel = self._keyboard_2_button
506        elif sel_name == 'iDevices':
507            sel = self._idevices_button
508        elif sel_name == 'Back':
509            sel = self._back_button
510        else:
511            sel = (
512                self._gamepads_button
513                if self._gamepads_button is not None
514                else self._back_button
515            )
516        bui.containerwidget(edit=self._root_widget, selected_child=sel)
517
518    # def _back(self) -> None:
519    #     # pylint: disable=cyclic-import
520    #     from bauiv1lib.settings.allsettings import AllSettingsWindow
521
522    #     # no-op if our underlying widget is dead or on its way out.
523    #     if not self._root_widget or self._root_widget.transitioning_out:
524    #         return
525
526    #     self._save_state()
527    #     bui.containerwidget(
528    #         edit=self._root_widget, transition=self._transition_out
529    #     )
530    #     assert bui.app.classic is not None
531    #     bui.app.ui_v1.set_main_window(
532    #         AllSettingsWindow(transition='in_left'),
533    #         from_window=self,
534    #         is_back=True,
535    #     )

Top level control settings window.

ControlsSettingsWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
 18    def __init__(
 19        self,
 20        transition: str | None = 'in_right',
 21        origin_widget: bui.Widget | None = None,
 22    ):
 23        # FIXME: should tidy up here.
 24        # pylint: disable=too-many-statements
 25        # pylint: disable=too-many-branches
 26        # pylint: disable=too-many-locals
 27        # pylint: disable=cyclic-import
 28
 29        self._have_selected_child = False
 30
 31        self._r = 'configControllersWindow'
 32        uiscale = bui.app.ui_v1.uiscale
 33        app = bui.app
 34        assert app.classic is not None
 35
 36        spacing = 50.0
 37        button_width = 350.0
 38        width = 800.0 if uiscale is bui.UIScale.SMALL else 460.0
 39        height = 300 if uiscale is bui.UIScale.SMALL else 130.0
 40
 41        yoffs = -60 if uiscale is bui.UIScale.SMALL else 0
 42        space_height = spacing * 0.3
 43
 44        # FIXME: should create vis settings under platform or
 45        # app-adapter to determine whether to show this stuff; not hard
 46        # code it.
 47
 48        show_gamepads = False
 49        platform = app.classic.platform
 50        subplatform = app.classic.subplatform
 51        non_vr_windows = platform == 'windows' and (
 52            subplatform != 'oculus' or not app.env.vr
 53        )
 54        if platform in ('linux', 'android', 'mac') or non_vr_windows:
 55            show_gamepads = True
 56            height += spacing
 57
 58        show_touch = False
 59        if bs.have_touchscreen_input():
 60            show_touch = True
 61            height += spacing
 62
 63        show_space_1 = False
 64        if show_gamepads or show_touch:
 65            show_space_1 = True
 66            height += space_height
 67
 68        show_keyboard = False
 69        if bs.getinputdevice('Keyboard', '#1', doraise=False) is not None:
 70            show_keyboard = True
 71            height += spacing
 72        show_keyboard_p2 = False if app.env.vr else show_keyboard
 73        if show_keyboard_p2:
 74            height += spacing
 75
 76        show_space_2 = False
 77        if show_keyboard:
 78            show_space_2 = True
 79            height += space_height
 80
 81        if bool(True):
 82            show_remote = True
 83            height += spacing
 84        else:
 85            show_remote = False
 86
 87        # On windows (outside of oculus/vr), show an option to disable xinput.
 88        show_xinput_toggle = False
 89        if platform == 'windows' and not app.env.vr:
 90            show_xinput_toggle = True
 91
 92        # On mac builds, show an option to switch between generic and
 93        # made-for-iOS/Mac systems
 94        # (we can run into problems where devices register as one of each
 95        # type otherwise)..
 96        # UPDATE: We always use the apple system these days (which should
 97        # support older controllers). So no need for a switch.
 98        show_mac_controller_subsystem = False
 99        # if platform == 'mac' and bui.is_xcode_build():
100        #     show_mac_controller_subsystem = True
101
102        if show_mac_controller_subsystem:
103            height += spacing * 1.5
104
105        if show_xinput_toggle:
106            height += spacing
107
108        assert bui.app.classic is not None
109        smallscale = 1.7 if show_keyboard else 2.2
110        super().__init__(
111            root_widget=bui.containerwidget(
112                size=(width, height),
113                stack_offset=(
114                    (0, -10) if uiscale is bui.UIScale.SMALL else (0, 0)
115                ),
116                scale=(
117                    smallscale
118                    if uiscale is bui.UIScale.SMALL
119                    else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
120                ),
121                toolbar_visibility=(
122                    'menu_minimal'
123                    if uiscale is bui.UIScale.SMALL
124                    else 'menu_full'
125                ),
126            ),
127            transition=transition,
128            origin_widget=origin_widget,
129        )
130
131        self._back_button: bui.Widget | None
132        if uiscale is bui.UIScale.SMALL:
133            bui.containerwidget(
134                edit=self._root_widget, on_cancel_call=self.main_window_back
135            )
136            self._back_button = None
137        else:
138            self._back_button = btn = bui.buttonwidget(
139                parent=self._root_widget,
140                position=(35, height - 60),
141                size=(140, 65),
142                scale=0.8,
143                text_scale=1.2,
144                autoselect=True,
145                label=bui.Lstr(resource='backText'),
146                button_type='back',
147                on_activate_call=self.main_window_back,
148            )
149            bui.containerwidget(edit=self._root_widget, cancel_button=btn)
150            bui.buttonwidget(
151                edit=btn,
152                button_type='backSmall',
153                size=(60, 60),
154                label=bui.charstr(bui.SpecialChar.BACK),
155            )
156
157        # We need these vars to exist even if the buttons don't.
158        self._gamepads_button: bui.Widget | None = None
159        self._touch_button: bui.Widget | None = None
160        self._keyboard_button: bui.Widget | None = None
161        self._keyboard_2_button: bui.Widget | None = None
162        self._idevices_button: bui.Widget | None = None
163
164        bui.textwidget(
165            parent=self._root_widget,
166            position=(0, height - 49 + yoffs),
167            size=(width, 25),
168            text=bui.Lstr(resource=f'{self._r}.titleText'),
169            color=bui.app.ui_v1.title_color,
170            h_align='center',
171            v_align='top',
172        )
173
174        v = height - 75 + yoffs
175        v -= spacing
176
177        if show_touch:
178            self._touch_button = btn = bui.buttonwidget(
179                parent=self._root_widget,
180                position=((width - button_width) / 2, v),
181                size=(button_width, 43),
182                autoselect=True,
183                label=bui.Lstr(resource=f'{self._r}.configureTouchText'),
184                on_activate_call=self._do_touchscreen,
185            )
186            bui.widget(
187                edit=btn,
188                right_widget=bui.get_special_widget('squad_button'),
189            )
190            if not self._have_selected_child:
191                bui.containerwidget(
192                    edit=self._root_widget, selected_child=self._touch_button
193                )
194                if self._back_button is not None:
195                    bui.widget(
196                        edit=self._back_button, down_widget=self._touch_button
197                    )
198                self._have_selected_child = True
199            v -= spacing
200
201        if show_gamepads:
202            self._gamepads_button = btn = bui.buttonwidget(
203                parent=self._root_widget,
204                position=((width - button_width) / 2 - 7, v),
205                size=(button_width, 43),
206                autoselect=True,
207                label=bui.Lstr(resource=f'{self._r}.configureControllersText'),
208                on_activate_call=self._do_gamepads,
209            )
210            bui.widget(
211                edit=btn,
212                right_widget=bui.get_special_widget('squad_button'),
213            )
214            if not self._have_selected_child:
215                bui.containerwidget(
216                    edit=self._root_widget, selected_child=self._gamepads_button
217                )
218                if self._back_button is not None:
219                    bui.widget(
220                        edit=self._back_button,
221                        down_widget=self._gamepads_button,
222                    )
223                self._have_selected_child = True
224            v -= spacing
225        else:
226            self._gamepads_button = None
227
228        if show_space_1:
229            v -= space_height
230
231        if show_keyboard:
232            self._keyboard_button = btn = bui.buttonwidget(
233                parent=self._root_widget,
234                position=((width - button_width) / 2 - 5, v),
235                size=(button_width, 43),
236                autoselect=True,
237                label=bui.Lstr(resource=f'{self._r}.configureKeyboardText'),
238                on_activate_call=self._config_keyboard,
239            )
240            bui.widget(
241                edit=self._keyboard_button, left_widget=self._keyboard_button
242            )
243            bui.widget(
244                edit=btn,
245                right_widget=bui.get_special_widget('squad_button'),
246            )
247            if not self._have_selected_child:
248                bui.containerwidget(
249                    edit=self._root_widget, selected_child=self._keyboard_button
250                )
251                if self._back_button is not None:
252                    bui.widget(
253                        edit=self._back_button,
254                        down_widget=self._keyboard_button,
255                    )
256                self._have_selected_child = True
257            v -= spacing
258        if show_keyboard_p2:
259            self._keyboard_2_button = bui.buttonwidget(
260                parent=self._root_widget,
261                position=((width - button_width) / 2 - 3, v),
262                size=(button_width, 43),
263                autoselect=True,
264                label=bui.Lstr(resource=f'{self._r}.configureKeyboard2Text'),
265                on_activate_call=self._config_keyboard2,
266            )
267            v -= spacing
268            bui.widget(
269                edit=self._keyboard_2_button,
270                left_widget=self._keyboard_2_button,
271            )
272        if show_space_2:
273            v -= space_height
274        if show_remote:
275            self._idevices_button = btn = bui.buttonwidget(
276                parent=self._root_widget,
277                position=((width - button_width) / 2 - 5, v),
278                size=(button_width, 43),
279                autoselect=True,
280                label=bui.Lstr(resource=f'{self._r}.configureMobileText'),
281                on_activate_call=self._do_mobile_devices,
282            )
283            bui.widget(
284                edit=self._idevices_button, left_widget=self._idevices_button
285            )
286            bui.widget(
287                edit=btn,
288                right_widget=bui.get_special_widget('squad_button'),
289            )
290            if not self._have_selected_child:
291                bui.containerwidget(
292                    edit=self._root_widget, selected_child=self._idevices_button
293                )
294                if self._back_button is not None:
295                    bui.widget(
296                        edit=self._back_button,
297                        down_widget=self._idevices_button,
298                    )
299                self._have_selected_child = True
300            v -= spacing
301
302        if show_xinput_toggle:
303
304            def do_toggle(value: bool) -> None:
305                bui.screenmessage(
306                    bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
307                    color=(1, 1, 0),
308                )
309                bui.getsound('gunCocking').play()
310                bui.set_low_level_config_value('enablexinput', not value)
311
312            xinput_checkbox = bui.checkboxwidget(
313                parent=self._root_widget,
314                position=(100, v + 3),
315                size=(120, 30),
316                value=(not bui.get_low_level_config_value('enablexinput', 1)),
317                maxwidth=200,
318                on_value_change_call=do_toggle,
319                text=bui.Lstr(resource='disableXInputText'),
320                autoselect=True,
321            )
322            bui.textwidget(
323                parent=self._root_widget,
324                position=(width * 0.5, v - 5),
325                size=(0, 0),
326                text=bui.Lstr(resource='disableXInputDescriptionText'),
327                scale=0.5,
328                h_align='center',
329                v_align='center',
330                color=bui.app.ui_v1.infotextcolor,
331                maxwidth=width * 0.8,
332            )
333            bui.widget(
334                edit=xinput_checkbox,
335                left_widget=xinput_checkbox,
336                right_widget=xinput_checkbox,
337            )
338            v -= spacing
339
340        if show_mac_controller_subsystem:
341            PopupMenu(
342                parent=self._root_widget,
343                position=(260, v - 10),
344                width=160,
345                button_size=(150, 50),
346                scale=1.5,
347                choices=['Classic', 'MFi', 'Both'],
348                choices_display=[
349                    bui.Lstr(resource='macControllerSubsystemClassicText'),
350                    bui.Lstr(resource='macControllerSubsystemMFiText'),
351                    bui.Lstr(resource='macControllerSubsystemBothText'),
352                ],
353                current_choice=bui.app.config.resolve(
354                    'Mac Controller Subsystem'
355                ),
356                on_value_change_call=self._set_mac_controller_subsystem,
357            )
358            bui.textwidget(
359                parent=self._root_widget,
360                position=(245, v + 13),
361                size=(0, 0),
362                text=bui.Lstr(resource='macControllerSubsystemTitleText'),
363                scale=1.0,
364                h_align='right',
365                v_align='center',
366                color=bui.app.ui_v1.infotextcolor,
367                maxwidth=180,
368            )
369            bui.textwidget(
370                parent=self._root_widget,
371                position=(width * 0.5, v - 20),
372                size=(0, 0),
373                text=bui.Lstr(resource='macControllerSubsystemDescriptionText'),
374                scale=0.5,
375                h_align='center',
376                v_align='center',
377                color=bui.app.ui_v1.infotextcolor,
378                maxwidth=width * 0.8,
379            )
380            v -= spacing * 1.5
381
382        self._restore_state()

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:
384    @override
385    def get_main_window_state(self) -> bui.MainWindowState:
386        # Support recreating our window for back/refresh purposes.
387        cls = type(self)
388        return bui.BasicMainWindowState(
389            create_call=lambda transition, origin_widget: cls(
390                transition=transition, origin_widget=origin_widget
391            )
392        )

Return a WindowState to recreate this window, if supported.

@override
def on_main_window_close(self) -> None:
394    @override
395    def on_main_window_close(self) -> None:
396        self._save_state()

Called before transitioning out a main window.

A good opportunity to save window state/etc.

Inherited Members
bauiv1._uitypes.MainWindow
main_window_back_state
main_window_close
can_change_main_window
main_window_back
main_window_replace
bauiv1._uitypes.Window
get_root_widget