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=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=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=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=self._r + '.configureKeyboardText'),
230                on_activate_call=self._config_keyboard,
231            )
232            if bui.app.ui_v1.use_toolbars:
233                bui.widget(
234                    edit=btn,
235                    right_widget=bui.get_special_widget('party_button'),
236                )
237            if not self._have_selected_child:
238                bui.containerwidget(
239                    edit=self._root_widget, selected_child=self._keyboard_button
240                )
241                bui.widget(
242                    edit=self._back_button, down_widget=self._keyboard_button
243                )
244                self._have_selected_child = True
245            v -= spacing
246        if show_keyboard_p2:
247            self._keyboard_2_button = bui.buttonwidget(
248                parent=self._root_widget,
249                position=((width - button_width) / 2 - 3, v),
250                size=(button_width, 43),
251                autoselect=True,
252                label=bui.Lstr(resource=self._r + '.configureKeyboard2Text'),
253                on_activate_call=self._config_keyboard2,
254            )
255            v -= spacing
256        if show_space_2:
257            v -= space_height
258        if show_remote:
259            self._idevices_button = btn = bui.buttonwidget(
260                parent=self._root_widget,
261                position=((width - button_width) / 2 - 5, v),
262                size=(button_width, 43),
263                autoselect=True,
264                label=bui.Lstr(resource=self._r + '.configureMobileText'),
265                on_activate_call=self._do_mobile_devices,
266            )
267            if bui.app.ui_v1.use_toolbars:
268                bui.widget(
269                    edit=btn,
270                    right_widget=bui.get_special_widget('party_button'),
271                )
272            if not self._have_selected_child:
273                bui.containerwidget(
274                    edit=self._root_widget, selected_child=self._idevices_button
275                )
276                bui.widget(
277                    edit=self._back_button, down_widget=self._idevices_button
278                )
279                self._have_selected_child = True
280            v -= spacing
281
282        if show_xinput_toggle:
283
284            def do_toggle(value: bool) -> None:
285                bui.screenmessage(
286                    bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
287                    color=(1, 1, 0),
288                )
289                bui.getsound('gunCocking').play()
290                bui.set_low_level_config_value('enablexinput', not value)
291
292            bui.checkboxwidget(
293                parent=self._root_widget,
294                position=(100, v + 3),
295                size=(120, 30),
296                value=(not bui.get_low_level_config_value('enablexinput', 1)),
297                maxwidth=200,
298                on_value_change_call=do_toggle,
299                text=bui.Lstr(resource='disableXInputText'),
300                autoselect=True,
301            )
302            bui.textwidget(
303                parent=self._root_widget,
304                position=(width * 0.5, v - 5),
305                size=(0, 0),
306                text=bui.Lstr(resource='disableXInputDescriptionText'),
307                scale=0.5,
308                h_align='center',
309                v_align='center',
310                color=bui.app.ui_v1.infotextcolor,
311                maxwidth=width * 0.8,
312            )
313            v -= spacing
314
315        if show_mac_controller_subsystem:
316            PopupMenu(
317                parent=self._root_widget,
318                position=(260, v - 10),
319                width=160,
320                button_size=(150, 50),
321                scale=1.5,
322                choices=['Classic', 'MFi', 'Both'],
323                choices_display=[
324                    bui.Lstr(resource='macControllerSubsystemClassicText'),
325                    bui.Lstr(resource='macControllerSubsystemMFiText'),
326                    bui.Lstr(resource='macControllerSubsystemBothText'),
327                ],
328                current_choice=bui.app.config.resolve(
329                    'Mac Controller Subsystem'
330                ),
331                on_value_change_call=self._set_mac_controller_subsystem,
332            )
333            bui.textwidget(
334                parent=self._root_widget,
335                position=(245, v + 13),
336                size=(0, 0),
337                text=bui.Lstr(resource='macControllerSubsystemTitleText'),
338                scale=1.0,
339                h_align='right',
340                v_align='center',
341                color=bui.app.ui_v1.infotextcolor,
342                maxwidth=180,
343            )
344            bui.textwidget(
345                parent=self._root_widget,
346                position=(width * 0.5, v - 20),
347                size=(0, 0),
348                text=bui.Lstr(resource='macControllerSubsystemDescriptionText'),
349                scale=0.5,
350                h_align='center',
351                v_align='center',
352                color=bui.app.ui_v1.infotextcolor,
353                maxwidth=width * 0.8,
354            )
355            v -= spacing * 1.5
356
357        self._restore_state()
358
359    def _set_mac_controller_subsystem(self, val: str) -> None:
360        cfg = bui.app.config
361        cfg['Mac Controller Subsystem'] = val
362        cfg.apply_and_commit()
363
364    def _config_keyboard(self) -> None:
365        # pylint: disable=cyclic-import
366        from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
367
368        # no-op if our underlying widget is dead or on its way out.
369        if not self._root_widget or self._root_widget.transitioning_out:
370            return
371
372        self._save_state()
373        bui.containerwidget(edit=self._root_widget, transition='out_left')
374        assert bui.app.classic is not None
375        bui.app.ui_v1.set_main_menu_window(
376            ConfigKeyboardWindow(
377                bs.getinputdevice('Keyboard', '#1')
378            ).get_root_widget(),
379            from_window=self._root_widget,
380        )
381
382    def _config_keyboard2(self) -> None:
383        # pylint: disable=cyclic-import
384        from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
385
386        # no-op if our underlying widget is dead or on its way out.
387        if not self._root_widget or self._root_widget.transitioning_out:
388            return
389
390        self._save_state()
391        bui.containerwidget(edit=self._root_widget, transition='out_left')
392        assert bui.app.classic is not None
393        bui.app.ui_v1.set_main_menu_window(
394            ConfigKeyboardWindow(
395                bs.getinputdevice('Keyboard', '#2')
396            ).get_root_widget(),
397            from_window=self._root_widget,
398        )
399
400    def _do_mobile_devices(self) -> None:
401        # pylint: disable=cyclic-import
402        from bauiv1lib.settings.remoteapp import RemoteAppSettingsWindow
403
404        # no-op if our underlying widget is dead or on its way out.
405        if not self._root_widget or self._root_widget.transitioning_out:
406            return
407
408        self._save_state()
409        bui.containerwidget(edit=self._root_widget, transition='out_left')
410        assert bui.app.classic is not None
411        bui.app.ui_v1.set_main_menu_window(
412            RemoteAppSettingsWindow().get_root_widget(),
413            from_window=self._root_widget,
414        )
415
416    def _do_gamepads(self) -> None:
417        # pylint: disable=cyclic-import
418        from bauiv1lib.settings.gamepadselect import GamepadSelectWindow
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            GamepadSelectWindow().get_root_widget(),
429            from_window=self._root_widget,
430        )
431
432    def _do_touchscreen(self) -> None:
433        # pylint: disable=cyclic-import
434        from bauiv1lib.settings.touchscreen import TouchscreenSettingsWindow
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            TouchscreenSettingsWindow().get_root_widget(),
445            from_window=self._root_widget,
446        )
447
448    def _save_state(self) -> None:
449        sel = self._root_widget.get_selected_child()
450        if sel == self._gamepads_button:
451            sel_name = 'GamePads'
452        elif sel == self._touch_button:
453            sel_name = 'Touch'
454        elif sel == self._keyboard_button:
455            sel_name = 'Keyboard'
456        elif sel == self._keyboard_2_button:
457            sel_name = 'Keyboard2'
458        elif sel == self._idevices_button:
459            sel_name = 'iDevices'
460        else:
461            sel_name = 'Back'
462        assert bui.app.classic is not None
463        bui.app.ui_v1.window_states[type(self)] = sel_name
464
465    def _restore_state(self) -> None:
466        assert bui.app.classic is not None
467        sel_name = bui.app.ui_v1.window_states.get(type(self))
468        if sel_name == 'GamePads':
469            sel = self._gamepads_button
470        elif sel_name == 'Touch':
471            sel = self._touch_button
472        elif sel_name == 'Keyboard':
473            sel = self._keyboard_button
474        elif sel_name == 'Keyboard2':
475            sel = self._keyboard_2_button
476        elif sel_name == 'iDevices':
477            sel = self._idevices_button
478        elif sel_name == 'Back':
479            sel = self._back_button
480        else:
481            sel = (
482                self._gamepads_button
483                if self._gamepads_button is not None
484                else self._back_button
485            )
486        bui.containerwidget(edit=self._root_widget, selected_child=sel)
487
488    def _back(self) -> None:
489        # pylint: disable=cyclic-import
490        from bauiv1lib.settings.allsettings import AllSettingsWindow
491
492        # no-op if our underlying widget is dead or on its way out.
493        if not self._root_widget or self._root_widget.transitioning_out:
494            return
495
496        self._save_state()
497        bui.containerwidget(
498            edit=self._root_widget, transition=self._transition_out
499        )
500        assert bui.app.classic is not None
501        bui.app.ui_v1.set_main_menu_window(
502            AllSettingsWindow(transition='in_left').get_root_widget(),
503            from_window=self._root_widget,
504        )
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=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=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=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=self._r + '.configureKeyboardText'),
231                on_activate_call=self._config_keyboard,
232            )
233            if bui.app.ui_v1.use_toolbars:
234                bui.widget(
235                    edit=btn,
236                    right_widget=bui.get_special_widget('party_button'),
237                )
238            if not self._have_selected_child:
239                bui.containerwidget(
240                    edit=self._root_widget, selected_child=self._keyboard_button
241                )
242                bui.widget(
243                    edit=self._back_button, down_widget=self._keyboard_button
244                )
245                self._have_selected_child = True
246            v -= spacing
247        if show_keyboard_p2:
248            self._keyboard_2_button = bui.buttonwidget(
249                parent=self._root_widget,
250                position=((width - button_width) / 2 - 3, v),
251                size=(button_width, 43),
252                autoselect=True,
253                label=bui.Lstr(resource=self._r + '.configureKeyboard2Text'),
254                on_activate_call=self._config_keyboard2,
255            )
256            v -= spacing
257        if show_space_2:
258            v -= space_height
259        if show_remote:
260            self._idevices_button = btn = bui.buttonwidget(
261                parent=self._root_widget,
262                position=((width - button_width) / 2 - 5, v),
263                size=(button_width, 43),
264                autoselect=True,
265                label=bui.Lstr(resource=self._r + '.configureMobileText'),
266                on_activate_call=self._do_mobile_devices,
267            )
268            if bui.app.ui_v1.use_toolbars:
269                bui.widget(
270                    edit=btn,
271                    right_widget=bui.get_special_widget('party_button'),
272                )
273            if not self._have_selected_child:
274                bui.containerwidget(
275                    edit=self._root_widget, selected_child=self._idevices_button
276                )
277                bui.widget(
278                    edit=self._back_button, down_widget=self._idevices_button
279                )
280                self._have_selected_child = True
281            v -= spacing
282
283        if show_xinput_toggle:
284
285            def do_toggle(value: bool) -> None:
286                bui.screenmessage(
287                    bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
288                    color=(1, 1, 0),
289                )
290                bui.getsound('gunCocking').play()
291                bui.set_low_level_config_value('enablexinput', not value)
292
293            bui.checkboxwidget(
294                parent=self._root_widget,
295                position=(100, v + 3),
296                size=(120, 30),
297                value=(not bui.get_low_level_config_value('enablexinput', 1)),
298                maxwidth=200,
299                on_value_change_call=do_toggle,
300                text=bui.Lstr(resource='disableXInputText'),
301                autoselect=True,
302            )
303            bui.textwidget(
304                parent=self._root_widget,
305                position=(width * 0.5, v - 5),
306                size=(0, 0),
307                text=bui.Lstr(resource='disableXInputDescriptionText'),
308                scale=0.5,
309                h_align='center',
310                v_align='center',
311                color=bui.app.ui_v1.infotextcolor,
312                maxwidth=width * 0.8,
313            )
314            v -= spacing
315
316        if show_mac_controller_subsystem:
317            PopupMenu(
318                parent=self._root_widget,
319                position=(260, v - 10),
320                width=160,
321                button_size=(150, 50),
322                scale=1.5,
323                choices=['Classic', 'MFi', 'Both'],
324                choices_display=[
325                    bui.Lstr(resource='macControllerSubsystemClassicText'),
326                    bui.Lstr(resource='macControllerSubsystemMFiText'),
327                    bui.Lstr(resource='macControllerSubsystemBothText'),
328                ],
329                current_choice=bui.app.config.resolve(
330                    'Mac Controller Subsystem'
331                ),
332                on_value_change_call=self._set_mac_controller_subsystem,
333            )
334            bui.textwidget(
335                parent=self._root_widget,
336                position=(245, v + 13),
337                size=(0, 0),
338                text=bui.Lstr(resource='macControllerSubsystemTitleText'),
339                scale=1.0,
340                h_align='right',
341                v_align='center',
342                color=bui.app.ui_v1.infotextcolor,
343                maxwidth=180,
344            )
345            bui.textwidget(
346                parent=self._root_widget,
347                position=(width * 0.5, v - 20),
348                size=(0, 0),
349                text=bui.Lstr(resource='macControllerSubsystemDescriptionText'),
350                scale=0.5,
351                h_align='center',
352                v_align='center',
353                color=bui.app.ui_v1.infotextcolor,
354                maxwidth=width * 0.8,
355            )
356            v -= spacing * 1.5
357
358        self._restore_state()
359
360    def _set_mac_controller_subsystem(self, val: str) -> None:
361        cfg = bui.app.config
362        cfg['Mac Controller Subsystem'] = val
363        cfg.apply_and_commit()
364
365    def _config_keyboard(self) -> None:
366        # pylint: disable=cyclic-import
367        from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
368
369        # no-op if our underlying widget is dead or on its way out.
370        if not self._root_widget or self._root_widget.transitioning_out:
371            return
372
373        self._save_state()
374        bui.containerwidget(edit=self._root_widget, transition='out_left')
375        assert bui.app.classic is not None
376        bui.app.ui_v1.set_main_menu_window(
377            ConfigKeyboardWindow(
378                bs.getinputdevice('Keyboard', '#1')
379            ).get_root_widget(),
380            from_window=self._root_widget,
381        )
382
383    def _config_keyboard2(self) -> None:
384        # pylint: disable=cyclic-import
385        from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
386
387        # no-op if our underlying widget is dead or on its way out.
388        if not self._root_widget or self._root_widget.transitioning_out:
389            return
390
391        self._save_state()
392        bui.containerwidget(edit=self._root_widget, transition='out_left')
393        assert bui.app.classic is not None
394        bui.app.ui_v1.set_main_menu_window(
395            ConfigKeyboardWindow(
396                bs.getinputdevice('Keyboard', '#2')
397            ).get_root_widget(),
398            from_window=self._root_widget,
399        )
400
401    def _do_mobile_devices(self) -> None:
402        # pylint: disable=cyclic-import
403        from bauiv1lib.settings.remoteapp import RemoteAppSettingsWindow
404
405        # no-op if our underlying widget is dead or on its way out.
406        if not self._root_widget or self._root_widget.transitioning_out:
407            return
408
409        self._save_state()
410        bui.containerwidget(edit=self._root_widget, transition='out_left')
411        assert bui.app.classic is not None
412        bui.app.ui_v1.set_main_menu_window(
413            RemoteAppSettingsWindow().get_root_widget(),
414            from_window=self._root_widget,
415        )
416
417    def _do_gamepads(self) -> None:
418        # pylint: disable=cyclic-import
419        from bauiv1lib.settings.gamepadselect import GamepadSelectWindow
420
421        # no-op if our underlying widget is dead or on its way out.
422        if not self._root_widget or self._root_widget.transitioning_out:
423            return
424
425        self._save_state()
426        bui.containerwidget(edit=self._root_widget, transition='out_left')
427        assert bui.app.classic is not None
428        bui.app.ui_v1.set_main_menu_window(
429            GamepadSelectWindow().get_root_widget(),
430            from_window=self._root_widget,
431        )
432
433    def _do_touchscreen(self) -> None:
434        # pylint: disable=cyclic-import
435        from bauiv1lib.settings.touchscreen import TouchscreenSettingsWindow
436
437        # no-op if our underlying widget is dead or on its way out.
438        if not self._root_widget or self._root_widget.transitioning_out:
439            return
440
441        self._save_state()
442        bui.containerwidget(edit=self._root_widget, transition='out_left')
443        assert bui.app.classic is not None
444        bui.app.ui_v1.set_main_menu_window(
445            TouchscreenSettingsWindow().get_root_widget(),
446            from_window=self._root_widget,
447        )
448
449    def _save_state(self) -> None:
450        sel = self._root_widget.get_selected_child()
451        if sel == self._gamepads_button:
452            sel_name = 'GamePads'
453        elif sel == self._touch_button:
454            sel_name = 'Touch'
455        elif sel == self._keyboard_button:
456            sel_name = 'Keyboard'
457        elif sel == self._keyboard_2_button:
458            sel_name = 'Keyboard2'
459        elif sel == self._idevices_button:
460            sel_name = 'iDevices'
461        else:
462            sel_name = 'Back'
463        assert bui.app.classic is not None
464        bui.app.ui_v1.window_states[type(self)] = sel_name
465
466    def _restore_state(self) -> None:
467        assert bui.app.classic is not None
468        sel_name = bui.app.ui_v1.window_states.get(type(self))
469        if sel_name == 'GamePads':
470            sel = self._gamepads_button
471        elif sel_name == 'Touch':
472            sel = self._touch_button
473        elif sel_name == 'Keyboard':
474            sel = self._keyboard_button
475        elif sel_name == 'Keyboard2':
476            sel = self._keyboard_2_button
477        elif sel_name == 'iDevices':
478            sel = self._idevices_button
479        elif sel_name == 'Back':
480            sel = self._back_button
481        else:
482            sel = (
483                self._gamepads_button
484                if self._gamepads_button is not None
485                else self._back_button
486            )
487        bui.containerwidget(edit=self._root_widget, selected_child=sel)
488
489    def _back(self) -> None:
490        # pylint: disable=cyclic-import
491        from bauiv1lib.settings.allsettings import AllSettingsWindow
492
493        # no-op if our underlying widget is dead or on its way out.
494        if not self._root_widget or self._root_widget.transitioning_out:
495            return
496
497        self._save_state()
498        bui.containerwidget(
499            edit=self._root_widget, transition=self._transition_out
500        )
501        assert bui.app.classic is not None
502        bui.app.ui_v1.set_main_menu_window(
503            AllSettingsWindow(transition='in_left').get_root_widget(),
504            from_window=self._root_widget,
505        )

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=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=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=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=self._r + '.configureKeyboardText'),
231                on_activate_call=self._config_keyboard,
232            )
233            if bui.app.ui_v1.use_toolbars:
234                bui.widget(
235                    edit=btn,
236                    right_widget=bui.get_special_widget('party_button'),
237                )
238            if not self._have_selected_child:
239                bui.containerwidget(
240                    edit=self._root_widget, selected_child=self._keyboard_button
241                )
242                bui.widget(
243                    edit=self._back_button, down_widget=self._keyboard_button
244                )
245                self._have_selected_child = True
246            v -= spacing
247        if show_keyboard_p2:
248            self._keyboard_2_button = bui.buttonwidget(
249                parent=self._root_widget,
250                position=((width - button_width) / 2 - 3, v),
251                size=(button_width, 43),
252                autoselect=True,
253                label=bui.Lstr(resource=self._r + '.configureKeyboard2Text'),
254                on_activate_call=self._config_keyboard2,
255            )
256            v -= spacing
257        if show_space_2:
258            v -= space_height
259        if show_remote:
260            self._idevices_button = btn = bui.buttonwidget(
261                parent=self._root_widget,
262                position=((width - button_width) / 2 - 5, v),
263                size=(button_width, 43),
264                autoselect=True,
265                label=bui.Lstr(resource=self._r + '.configureMobileText'),
266                on_activate_call=self._do_mobile_devices,
267            )
268            if bui.app.ui_v1.use_toolbars:
269                bui.widget(
270                    edit=btn,
271                    right_widget=bui.get_special_widget('party_button'),
272                )
273            if not self._have_selected_child:
274                bui.containerwidget(
275                    edit=self._root_widget, selected_child=self._idevices_button
276                )
277                bui.widget(
278                    edit=self._back_button, down_widget=self._idevices_button
279                )
280                self._have_selected_child = True
281            v -= spacing
282
283        if show_xinput_toggle:
284
285            def do_toggle(value: bool) -> None:
286                bui.screenmessage(
287                    bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
288                    color=(1, 1, 0),
289                )
290                bui.getsound('gunCocking').play()
291                bui.set_low_level_config_value('enablexinput', not value)
292
293            bui.checkboxwidget(
294                parent=self._root_widget,
295                position=(100, v + 3),
296                size=(120, 30),
297                value=(not bui.get_low_level_config_value('enablexinput', 1)),
298                maxwidth=200,
299                on_value_change_call=do_toggle,
300                text=bui.Lstr(resource='disableXInputText'),
301                autoselect=True,
302            )
303            bui.textwidget(
304                parent=self._root_widget,
305                position=(width * 0.5, v - 5),
306                size=(0, 0),
307                text=bui.Lstr(resource='disableXInputDescriptionText'),
308                scale=0.5,
309                h_align='center',
310                v_align='center',
311                color=bui.app.ui_v1.infotextcolor,
312                maxwidth=width * 0.8,
313            )
314            v -= spacing
315
316        if show_mac_controller_subsystem:
317            PopupMenu(
318                parent=self._root_widget,
319                position=(260, v - 10),
320                width=160,
321                button_size=(150, 50),
322                scale=1.5,
323                choices=['Classic', 'MFi', 'Both'],
324                choices_display=[
325                    bui.Lstr(resource='macControllerSubsystemClassicText'),
326                    bui.Lstr(resource='macControllerSubsystemMFiText'),
327                    bui.Lstr(resource='macControllerSubsystemBothText'),
328                ],
329                current_choice=bui.app.config.resolve(
330                    'Mac Controller Subsystem'
331                ),
332                on_value_change_call=self._set_mac_controller_subsystem,
333            )
334            bui.textwidget(
335                parent=self._root_widget,
336                position=(245, v + 13),
337                size=(0, 0),
338                text=bui.Lstr(resource='macControllerSubsystemTitleText'),
339                scale=1.0,
340                h_align='right',
341                v_align='center',
342                color=bui.app.ui_v1.infotextcolor,
343                maxwidth=180,
344            )
345            bui.textwidget(
346                parent=self._root_widget,
347                position=(width * 0.5, v - 20),
348                size=(0, 0),
349                text=bui.Lstr(resource='macControllerSubsystemDescriptionText'),
350                scale=0.5,
351                h_align='center',
352                v_align='center',
353                color=bui.app.ui_v1.infotextcolor,
354                maxwidth=width * 0.8,
355            )
356            v -= spacing * 1.5
357
358        self._restore_state()
Inherited Members
bauiv1._uitypes.Window
get_root_widget