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

Top level control settings window.

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