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
128                    if uiscale is bui.UIScale.MEDIUM
129                    else 1.0
130                ),
131            )
132        )
133        self._back_button = btn = bui.buttonwidget(
134            parent=self._root_widget,
135            position=(35, height - 60),
136            size=(140, 65),
137            scale=0.8,
138            text_scale=1.2,
139            autoselect=True,
140            label=bui.Lstr(resource='backText'),
141            button_type='back',
142            on_activate_call=self._back,
143        )
144        bui.containerwidget(edit=self._root_widget, cancel_button=btn)
145
146        # We need these vars to exist even if the buttons don't.
147        self._gamepads_button: bui.Widget | None = None
148        self._touch_button: bui.Widget | None = None
149        self._keyboard_button: bui.Widget | None = None
150        self._keyboard_2_button: bui.Widget | None = None
151        self._idevices_button: bui.Widget | None = None
152
153        bui.textwidget(
154            parent=self._root_widget,
155            position=(0, height - 49),
156            size=(width, 25),
157            text=bui.Lstr(resource=self._r + '.titleText'),
158            color=bui.app.ui_v1.title_color,
159            h_align='center',
160            v_align='top',
161        )
162        bui.buttonwidget(
163            edit=btn,
164            button_type='backSmall',
165            size=(60, 60),
166            label=bui.charstr(bui.SpecialChar.BACK),
167        )
168
169        v = height - 75
170        v -= spacing
171
172        if show_touch:
173            self._touch_button = btn = bui.buttonwidget(
174                parent=self._root_widget,
175                position=((width - button_width) / 2, v),
176                size=(button_width, 43),
177                autoselect=True,
178                label=bui.Lstr(resource=self._r + '.configureTouchText'),
179                on_activate_call=self._do_touchscreen,
180            )
181            if bui.app.ui_v1.use_toolbars:
182                bui.widget(
183                    edit=btn,
184                    right_widget=bui.get_special_widget('party_button'),
185                )
186            if not self._have_selected_child:
187                bui.containerwidget(
188                    edit=self._root_widget, selected_child=self._touch_button
189                )
190                bui.widget(
191                    edit=self._back_button, down_widget=self._touch_button
192                )
193                self._have_selected_child = True
194            v -= spacing
195
196        if show_gamepads:
197            self._gamepads_button = btn = bui.buttonwidget(
198                parent=self._root_widget,
199                position=((width - button_width) / 2 - 7, v),
200                size=(button_width, 43),
201                autoselect=True,
202                label=bui.Lstr(resource=self._r + '.configureControllersText'),
203                on_activate_call=self._do_gamepads,
204            )
205            if bui.app.ui_v1.use_toolbars:
206                bui.widget(
207                    edit=btn,
208                    right_widget=bui.get_special_widget('party_button'),
209                )
210            if not self._have_selected_child:
211                bui.containerwidget(
212                    edit=self._root_widget, selected_child=self._gamepads_button
213                )
214                bui.widget(
215                    edit=self._back_button, down_widget=self._gamepads_button
216                )
217                self._have_selected_child = True
218            v -= spacing
219        else:
220            self._gamepads_button = None
221
222        if show_space_1:
223            v -= space_height
224
225        if show_keyboard:
226            self._keyboard_button = btn = bui.buttonwidget(
227                parent=self._root_widget,
228                position=((width - button_width) / 2 + 5, v),
229                size=(button_width, 43),
230                autoselect=True,
231                label=bui.Lstr(resource=self._r + '.configureKeyboardText'),
232                on_activate_call=self._config_keyboard,
233            )
234            if bui.app.ui_v1.use_toolbars:
235                bui.widget(
236                    edit=btn,
237                    right_widget=bui.get_special_widget('party_button'),
238                )
239            if not self._have_selected_child:
240                bui.containerwidget(
241                    edit=self._root_widget, selected_child=self._keyboard_button
242                )
243                bui.widget(
244                    edit=self._back_button, down_widget=self._keyboard_button
245                )
246                self._have_selected_child = True
247            v -= spacing
248        if show_keyboard_p2:
249            self._keyboard_2_button = bui.buttonwidget(
250                parent=self._root_widget,
251                position=((width - button_width) / 2 - 3, v),
252                size=(button_width, 43),
253                autoselect=True,
254                label=bui.Lstr(resource=self._r + '.configureKeyboard2Text'),
255                on_activate_call=self._config_keyboard2,
256            )
257            v -= spacing
258        if show_space_2:
259            v -= space_height
260        if show_remote:
261            self._idevices_button = btn = bui.buttonwidget(
262                parent=self._root_widget,
263                position=((width - button_width) / 2 - 5, v),
264                size=(button_width, 43),
265                autoselect=True,
266                label=bui.Lstr(resource=self._r + '.configureMobileText'),
267                on_activate_call=self._do_mobile_devices,
268            )
269            if bui.app.ui_v1.use_toolbars:
270                bui.widget(
271                    edit=btn,
272                    right_widget=bui.get_special_widget('party_button'),
273                )
274            if not self._have_selected_child:
275                bui.containerwidget(
276                    edit=self._root_widget, selected_child=self._idevices_button
277                )
278                bui.widget(
279                    edit=self._back_button, down_widget=self._idevices_button
280                )
281                self._have_selected_child = True
282            v -= spacing
283
284        if show_xinput_toggle:
285
286            def do_toggle(value: bool) -> None:
287                bui.screenmessage(
288                    bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
289                    color=(1, 1, 0),
290                )
291                bui.getsound('gunCocking').play()
292                bui.set_low_level_config_value('enablexinput', not value)
293
294            bui.checkboxwidget(
295                parent=self._root_widget,
296                position=(100, v + 3),
297                size=(120, 30),
298                value=(not bui.get_low_level_config_value('enablexinput', 1)),
299                maxwidth=200,
300                on_value_change_call=do_toggle,
301                text=bui.Lstr(resource='disableXInputText'),
302                autoselect=True,
303            )
304            bui.textwidget(
305                parent=self._root_widget,
306                position=(width * 0.5, v - 5),
307                size=(0, 0),
308                text=bui.Lstr(resource='disableXInputDescriptionText'),
309                scale=0.5,
310                h_align='center',
311                v_align='center',
312                color=bui.app.ui_v1.infotextcolor,
313                maxwidth=width * 0.8,
314            )
315            v -= spacing
316
317        if show_mac_controller_subsystem:
318            PopupMenu(
319                parent=self._root_widget,
320                position=(260, v - 10),
321                width=160,
322                button_size=(150, 50),
323                scale=1.5,
324                choices=['Classic', 'MFi', 'Both'],
325                choices_display=[
326                    bui.Lstr(resource='macControllerSubsystemClassicText'),
327                    bui.Lstr(resource='macControllerSubsystemMFiText'),
328                    bui.Lstr(resource='macControllerSubsystemBothText'),
329                ],
330                current_choice=bui.app.config.resolve(
331                    'Mac Controller Subsystem'
332                ),
333                on_value_change_call=self._set_mac_controller_subsystem,
334            )
335            bui.textwidget(
336                parent=self._root_widget,
337                position=(245, v + 13),
338                size=(0, 0),
339                text=bui.Lstr(resource='macControllerSubsystemTitleText'),
340                scale=1.0,
341                h_align='right',
342                v_align='center',
343                color=bui.app.ui_v1.infotextcolor,
344                maxwidth=180,
345            )
346            bui.textwidget(
347                parent=self._root_widget,
348                position=(width * 0.5, v - 20),
349                size=(0, 0),
350                text=bui.Lstr(resource='macControllerSubsystemDescriptionText'),
351                scale=0.5,
352                h_align='center',
353                v_align='center',
354                color=bui.app.ui_v1.infotextcolor,
355                maxwidth=width * 0.8,
356            )
357            v -= spacing * 1.5
358
359        self._restore_state()
360
361    def _set_mac_controller_subsystem(self, val: str) -> None:
362        cfg = bui.app.config
363        cfg['Mac Controller Subsystem'] = val
364        cfg.apply_and_commit()
365
366    def _config_keyboard(self) -> None:
367        # pylint: disable=cyclic-import
368        from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
369
370        # no-op if our underlying widget is dead or on its way out.
371        if not self._root_widget or self._root_widget.transitioning_out:
372            return
373
374        self._save_state()
375        bui.containerwidget(edit=self._root_widget, transition='out_left')
376        assert bui.app.classic is not None
377        bui.app.ui_v1.set_main_menu_window(
378            ConfigKeyboardWindow(
379                bs.getinputdevice('Keyboard', '#1')
380            ).get_root_widget(),
381            from_window=self._root_widget,
382        )
383
384    def _config_keyboard2(self) -> None:
385        # pylint: disable=cyclic-import
386        from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
387
388        # no-op if our underlying widget is dead or on its way out.
389        if not self._root_widget or self._root_widget.transitioning_out:
390            return
391
392        self._save_state()
393        bui.containerwidget(edit=self._root_widget, transition='out_left')
394        assert bui.app.classic is not None
395        bui.app.ui_v1.set_main_menu_window(
396            ConfigKeyboardWindow(
397                bs.getinputdevice('Keyboard', '#2')
398            ).get_root_widget(),
399            from_window=self._root_widget,
400        )
401
402    def _do_mobile_devices(self) -> None:
403        # pylint: disable=cyclic-import
404        from bauiv1lib.settings.remoteapp import RemoteAppSettingsWindow
405
406        # no-op if our underlying widget is dead or on its way out.
407        if not self._root_widget or self._root_widget.transitioning_out:
408            return
409
410        self._save_state()
411        bui.containerwidget(edit=self._root_widget, transition='out_left')
412        assert bui.app.classic is not None
413        bui.app.ui_v1.set_main_menu_window(
414            RemoteAppSettingsWindow().get_root_widget(),
415            from_window=self._root_widget,
416        )
417
418    def _do_gamepads(self) -> None:
419        # pylint: disable=cyclic-import
420        from bauiv1lib.settings.gamepadselect import GamepadSelectWindow
421
422        # no-op if our underlying widget is dead or on its way out.
423        if not self._root_widget or self._root_widget.transitioning_out:
424            return
425
426        self._save_state()
427        bui.containerwidget(edit=self._root_widget, transition='out_left')
428        assert bui.app.classic is not None
429        bui.app.ui_v1.set_main_menu_window(
430            GamepadSelectWindow().get_root_widget(),
431            from_window=self._root_widget,
432        )
433
434    def _do_touchscreen(self) -> None:
435        # pylint: disable=cyclic-import
436        from bauiv1lib.settings.touchscreen import TouchscreenSettingsWindow
437
438        # no-op if our underlying widget is dead or on its way out.
439        if not self._root_widget or self._root_widget.transitioning_out:
440            return
441
442        self._save_state()
443        bui.containerwidget(edit=self._root_widget, transition='out_left')
444        assert bui.app.classic is not None
445        bui.app.ui_v1.set_main_menu_window(
446            TouchscreenSettingsWindow().get_root_widget(),
447            from_window=self._root_widget,
448        )
449
450    def _save_state(self) -> None:
451        sel = self._root_widget.get_selected_child()
452        if sel == self._gamepads_button:
453            sel_name = 'GamePads'
454        elif sel == self._touch_button:
455            sel_name = 'Touch'
456        elif sel == self._keyboard_button:
457            sel_name = 'Keyboard'
458        elif sel == self._keyboard_2_button:
459            sel_name = 'Keyboard2'
460        elif sel == self._idevices_button:
461            sel_name = 'iDevices'
462        else:
463            sel_name = 'Back'
464        assert bui.app.classic is not None
465        bui.app.ui_v1.window_states[type(self)] = sel_name
466
467    def _restore_state(self) -> None:
468        assert bui.app.classic is not None
469        sel_name = bui.app.ui_v1.window_states.get(type(self))
470        if sel_name == 'GamePads':
471            sel = self._gamepads_button
472        elif sel_name == 'Touch':
473            sel = self._touch_button
474        elif sel_name == 'Keyboard':
475            sel = self._keyboard_button
476        elif sel_name == 'Keyboard2':
477            sel = self._keyboard_2_button
478        elif sel_name == 'iDevices':
479            sel = self._idevices_button
480        elif sel_name == 'Back':
481            sel = self._back_button
482        else:
483            sel = (
484                self._gamepads_button
485                if self._gamepads_button is not None
486                else self._back_button
487            )
488        bui.containerwidget(edit=self._root_widget, selected_child=sel)
489
490    def _back(self) -> None:
491        # pylint: disable=cyclic-import
492        from bauiv1lib.settings.allsettings import AllSettingsWindow
493
494        # no-op if our underlying widget is dead or on its way out.
495        if not self._root_widget or self._root_widget.transitioning_out:
496            return
497
498        self._save_state()
499        bui.containerwidget(
500            edit=self._root_widget, transition=self._transition_out
501        )
502        assert bui.app.classic is not None
503        bui.app.ui_v1.set_main_menu_window(
504            AllSettingsWindow(transition='in_left').get_root_widget(),
505            from_window=self._root_widget,
506        )
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
129                    if uiscale is bui.UIScale.MEDIUM
130                    else 1.0
131                ),
132            )
133        )
134        self._back_button = btn = bui.buttonwidget(
135            parent=self._root_widget,
136            position=(35, height - 60),
137            size=(140, 65),
138            scale=0.8,
139            text_scale=1.2,
140            autoselect=True,
141            label=bui.Lstr(resource='backText'),
142            button_type='back',
143            on_activate_call=self._back,
144        )
145        bui.containerwidget(edit=self._root_widget, cancel_button=btn)
146
147        # We need these vars to exist even if the buttons don't.
148        self._gamepads_button: bui.Widget | None = None
149        self._touch_button: bui.Widget | None = None
150        self._keyboard_button: bui.Widget | None = None
151        self._keyboard_2_button: bui.Widget | None = None
152        self._idevices_button: bui.Widget | None = None
153
154        bui.textwidget(
155            parent=self._root_widget,
156            position=(0, height - 49),
157            size=(width, 25),
158            text=bui.Lstr(resource=self._r + '.titleText'),
159            color=bui.app.ui_v1.title_color,
160            h_align='center',
161            v_align='top',
162        )
163        bui.buttonwidget(
164            edit=btn,
165            button_type='backSmall',
166            size=(60, 60),
167            label=bui.charstr(bui.SpecialChar.BACK),
168        )
169
170        v = height - 75
171        v -= spacing
172
173        if show_touch:
174            self._touch_button = btn = bui.buttonwidget(
175                parent=self._root_widget,
176                position=((width - button_width) / 2, v),
177                size=(button_width, 43),
178                autoselect=True,
179                label=bui.Lstr(resource=self._r + '.configureTouchText'),
180                on_activate_call=self._do_touchscreen,
181            )
182            if bui.app.ui_v1.use_toolbars:
183                bui.widget(
184                    edit=btn,
185                    right_widget=bui.get_special_widget('party_button'),
186                )
187            if not self._have_selected_child:
188                bui.containerwidget(
189                    edit=self._root_widget, selected_child=self._touch_button
190                )
191                bui.widget(
192                    edit=self._back_button, down_widget=self._touch_button
193                )
194                self._have_selected_child = True
195            v -= spacing
196
197        if show_gamepads:
198            self._gamepads_button = btn = bui.buttonwidget(
199                parent=self._root_widget,
200                position=((width - button_width) / 2 - 7, v),
201                size=(button_width, 43),
202                autoselect=True,
203                label=bui.Lstr(resource=self._r + '.configureControllersText'),
204                on_activate_call=self._do_gamepads,
205            )
206            if bui.app.ui_v1.use_toolbars:
207                bui.widget(
208                    edit=btn,
209                    right_widget=bui.get_special_widget('party_button'),
210                )
211            if not self._have_selected_child:
212                bui.containerwidget(
213                    edit=self._root_widget, selected_child=self._gamepads_button
214                )
215                bui.widget(
216                    edit=self._back_button, down_widget=self._gamepads_button
217                )
218                self._have_selected_child = True
219            v -= spacing
220        else:
221            self._gamepads_button = None
222
223        if show_space_1:
224            v -= space_height
225
226        if show_keyboard:
227            self._keyboard_button = btn = bui.buttonwidget(
228                parent=self._root_widget,
229                position=((width - button_width) / 2 + 5, v),
230                size=(button_width, 43),
231                autoselect=True,
232                label=bui.Lstr(resource=self._r + '.configureKeyboardText'),
233                on_activate_call=self._config_keyboard,
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=self._r + '.configureKeyboard2Text'),
256                on_activate_call=self._config_keyboard2,
257            )
258            v -= spacing
259        if show_space_2:
260            v -= space_height
261        if show_remote:
262            self._idevices_button = btn = bui.buttonwidget(
263                parent=self._root_widget,
264                position=((width - button_width) / 2 - 5, v),
265                size=(button_width, 43),
266                autoselect=True,
267                label=bui.Lstr(resource=self._r + '.configureMobileText'),
268                on_activate_call=self._do_mobile_devices,
269            )
270            if bui.app.ui_v1.use_toolbars:
271                bui.widget(
272                    edit=btn,
273                    right_widget=bui.get_special_widget('party_button'),
274                )
275            if not self._have_selected_child:
276                bui.containerwidget(
277                    edit=self._root_widget, selected_child=self._idevices_button
278                )
279                bui.widget(
280                    edit=self._back_button, down_widget=self._idevices_button
281                )
282                self._have_selected_child = True
283            v -= spacing
284
285        if show_xinput_toggle:
286
287            def do_toggle(value: bool) -> None:
288                bui.screenmessage(
289                    bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
290                    color=(1, 1, 0),
291                )
292                bui.getsound('gunCocking').play()
293                bui.set_low_level_config_value('enablexinput', not value)
294
295            bui.checkboxwidget(
296                parent=self._root_widget,
297                position=(100, v + 3),
298                size=(120, 30),
299                value=(not bui.get_low_level_config_value('enablexinput', 1)),
300                maxwidth=200,
301                on_value_change_call=do_toggle,
302                text=bui.Lstr(resource='disableXInputText'),
303                autoselect=True,
304            )
305            bui.textwidget(
306                parent=self._root_widget,
307                position=(width * 0.5, v - 5),
308                size=(0, 0),
309                text=bui.Lstr(resource='disableXInputDescriptionText'),
310                scale=0.5,
311                h_align='center',
312                v_align='center',
313                color=bui.app.ui_v1.infotextcolor,
314                maxwidth=width * 0.8,
315            )
316            v -= spacing
317
318        if show_mac_controller_subsystem:
319            PopupMenu(
320                parent=self._root_widget,
321                position=(260, v - 10),
322                width=160,
323                button_size=(150, 50),
324                scale=1.5,
325                choices=['Classic', 'MFi', 'Both'],
326                choices_display=[
327                    bui.Lstr(resource='macControllerSubsystemClassicText'),
328                    bui.Lstr(resource='macControllerSubsystemMFiText'),
329                    bui.Lstr(resource='macControllerSubsystemBothText'),
330                ],
331                current_choice=bui.app.config.resolve(
332                    'Mac Controller Subsystem'
333                ),
334                on_value_change_call=self._set_mac_controller_subsystem,
335            )
336            bui.textwidget(
337                parent=self._root_widget,
338                position=(245, v + 13),
339                size=(0, 0),
340                text=bui.Lstr(resource='macControllerSubsystemTitleText'),
341                scale=1.0,
342                h_align='right',
343                v_align='center',
344                color=bui.app.ui_v1.infotextcolor,
345                maxwidth=180,
346            )
347            bui.textwidget(
348                parent=self._root_widget,
349                position=(width * 0.5, v - 20),
350                size=(0, 0),
351                text=bui.Lstr(resource='macControllerSubsystemDescriptionText'),
352                scale=0.5,
353                h_align='center',
354                v_align='center',
355                color=bui.app.ui_v1.infotextcolor,
356                maxwidth=width * 0.8,
357            )
358            v -= spacing * 1.5
359
360        self._restore_state()
361
362    def _set_mac_controller_subsystem(self, val: str) -> None:
363        cfg = bui.app.config
364        cfg['Mac Controller Subsystem'] = val
365        cfg.apply_and_commit()
366
367    def _config_keyboard(self) -> None:
368        # pylint: disable=cyclic-import
369        from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
370
371        # no-op if our underlying widget is dead or on its way out.
372        if not self._root_widget or self._root_widget.transitioning_out:
373            return
374
375        self._save_state()
376        bui.containerwidget(edit=self._root_widget, transition='out_left')
377        assert bui.app.classic is not None
378        bui.app.ui_v1.set_main_menu_window(
379            ConfigKeyboardWindow(
380                bs.getinputdevice('Keyboard', '#1')
381            ).get_root_widget(),
382            from_window=self._root_widget,
383        )
384
385    def _config_keyboard2(self) -> None:
386        # pylint: disable=cyclic-import
387        from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
388
389        # no-op if our underlying widget is dead or on its way out.
390        if not self._root_widget or self._root_widget.transitioning_out:
391            return
392
393        self._save_state()
394        bui.containerwidget(edit=self._root_widget, transition='out_left')
395        assert bui.app.classic is not None
396        bui.app.ui_v1.set_main_menu_window(
397            ConfigKeyboardWindow(
398                bs.getinputdevice('Keyboard', '#2')
399            ).get_root_widget(),
400            from_window=self._root_widget,
401        )
402
403    def _do_mobile_devices(self) -> None:
404        # pylint: disable=cyclic-import
405        from bauiv1lib.settings.remoteapp import RemoteAppSettingsWindow
406
407        # no-op if our underlying widget is dead or on its way out.
408        if not self._root_widget or self._root_widget.transitioning_out:
409            return
410
411        self._save_state()
412        bui.containerwidget(edit=self._root_widget, transition='out_left')
413        assert bui.app.classic is not None
414        bui.app.ui_v1.set_main_menu_window(
415            RemoteAppSettingsWindow().get_root_widget(),
416            from_window=self._root_widget,
417        )
418
419    def _do_gamepads(self) -> None:
420        # pylint: disable=cyclic-import
421        from bauiv1lib.settings.gamepadselect import GamepadSelectWindow
422
423        # no-op if our underlying widget is dead or on its way out.
424        if not self._root_widget or self._root_widget.transitioning_out:
425            return
426
427        self._save_state()
428        bui.containerwidget(edit=self._root_widget, transition='out_left')
429        assert bui.app.classic is not None
430        bui.app.ui_v1.set_main_menu_window(
431            GamepadSelectWindow().get_root_widget(),
432            from_window=self._root_widget,
433        )
434
435    def _do_touchscreen(self) -> None:
436        # pylint: disable=cyclic-import
437        from bauiv1lib.settings.touchscreen import TouchscreenSettingsWindow
438
439        # no-op if our underlying widget is dead or on its way out.
440        if not self._root_widget or self._root_widget.transitioning_out:
441            return
442
443        self._save_state()
444        bui.containerwidget(edit=self._root_widget, transition='out_left')
445        assert bui.app.classic is not None
446        bui.app.ui_v1.set_main_menu_window(
447            TouchscreenSettingsWindow().get_root_widget(),
448            from_window=self._root_widget,
449        )
450
451    def _save_state(self) -> None:
452        sel = self._root_widget.get_selected_child()
453        if sel == self._gamepads_button:
454            sel_name = 'GamePads'
455        elif sel == self._touch_button:
456            sel_name = 'Touch'
457        elif sel == self._keyboard_button:
458            sel_name = 'Keyboard'
459        elif sel == self._keyboard_2_button:
460            sel_name = 'Keyboard2'
461        elif sel == self._idevices_button:
462            sel_name = 'iDevices'
463        else:
464            sel_name = 'Back'
465        assert bui.app.classic is not None
466        bui.app.ui_v1.window_states[type(self)] = sel_name
467
468    def _restore_state(self) -> None:
469        assert bui.app.classic is not None
470        sel_name = bui.app.ui_v1.window_states.get(type(self))
471        if sel_name == 'GamePads':
472            sel = self._gamepads_button
473        elif sel_name == 'Touch':
474            sel = self._touch_button
475        elif sel_name == 'Keyboard':
476            sel = self._keyboard_button
477        elif sel_name == 'Keyboard2':
478            sel = self._keyboard_2_button
479        elif sel_name == 'iDevices':
480            sel = self._idevices_button
481        elif sel_name == 'Back':
482            sel = self._back_button
483        else:
484            sel = (
485                self._gamepads_button
486                if self._gamepads_button is not None
487                else self._back_button
488            )
489        bui.containerwidget(edit=self._root_widget, selected_child=sel)
490
491    def _back(self) -> None:
492        # pylint: disable=cyclic-import
493        from bauiv1lib.settings.allsettings import AllSettingsWindow
494
495        # no-op if our underlying widget is dead or on its way out.
496        if not self._root_widget or self._root_widget.transitioning_out:
497            return
498
499        self._save_state()
500        bui.containerwidget(
501            edit=self._root_widget, transition=self._transition_out
502        )
503        assert bui.app.classic is not None
504        bui.app.ui_v1.set_main_menu_window(
505            AllSettingsWindow(transition='in_left').get_root_widget(),
506            from_window=self._root_widget,
507        )

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
129                    if uiscale is bui.UIScale.MEDIUM
130                    else 1.0
131                ),
132            )
133        )
134        self._back_button = btn = bui.buttonwidget(
135            parent=self._root_widget,
136            position=(35, height - 60),
137            size=(140, 65),
138            scale=0.8,
139            text_scale=1.2,
140            autoselect=True,
141            label=bui.Lstr(resource='backText'),
142            button_type='back',
143            on_activate_call=self._back,
144        )
145        bui.containerwidget(edit=self._root_widget, cancel_button=btn)
146
147        # We need these vars to exist even if the buttons don't.
148        self._gamepads_button: bui.Widget | None = None
149        self._touch_button: bui.Widget | None = None
150        self._keyboard_button: bui.Widget | None = None
151        self._keyboard_2_button: bui.Widget | None = None
152        self._idevices_button: bui.Widget | None = None
153
154        bui.textwidget(
155            parent=self._root_widget,
156            position=(0, height - 49),
157            size=(width, 25),
158            text=bui.Lstr(resource=self._r + '.titleText'),
159            color=bui.app.ui_v1.title_color,
160            h_align='center',
161            v_align='top',
162        )
163        bui.buttonwidget(
164            edit=btn,
165            button_type='backSmall',
166            size=(60, 60),
167            label=bui.charstr(bui.SpecialChar.BACK),
168        )
169
170        v = height - 75
171        v -= spacing
172
173        if show_touch:
174            self._touch_button = btn = bui.buttonwidget(
175                parent=self._root_widget,
176                position=((width - button_width) / 2, v),
177                size=(button_width, 43),
178                autoselect=True,
179                label=bui.Lstr(resource=self._r + '.configureTouchText'),
180                on_activate_call=self._do_touchscreen,
181            )
182            if bui.app.ui_v1.use_toolbars:
183                bui.widget(
184                    edit=btn,
185                    right_widget=bui.get_special_widget('party_button'),
186                )
187            if not self._have_selected_child:
188                bui.containerwidget(
189                    edit=self._root_widget, selected_child=self._touch_button
190                )
191                bui.widget(
192                    edit=self._back_button, down_widget=self._touch_button
193                )
194                self._have_selected_child = True
195            v -= spacing
196
197        if show_gamepads:
198            self._gamepads_button = btn = bui.buttonwidget(
199                parent=self._root_widget,
200                position=((width - button_width) / 2 - 7, v),
201                size=(button_width, 43),
202                autoselect=True,
203                label=bui.Lstr(resource=self._r + '.configureControllersText'),
204                on_activate_call=self._do_gamepads,
205            )
206            if bui.app.ui_v1.use_toolbars:
207                bui.widget(
208                    edit=btn,
209                    right_widget=bui.get_special_widget('party_button'),
210                )
211            if not self._have_selected_child:
212                bui.containerwidget(
213                    edit=self._root_widget, selected_child=self._gamepads_button
214                )
215                bui.widget(
216                    edit=self._back_button, down_widget=self._gamepads_button
217                )
218                self._have_selected_child = True
219            v -= spacing
220        else:
221            self._gamepads_button = None
222
223        if show_space_1:
224            v -= space_height
225
226        if show_keyboard:
227            self._keyboard_button = btn = bui.buttonwidget(
228                parent=self._root_widget,
229                position=((width - button_width) / 2 + 5, v),
230                size=(button_width, 43),
231                autoselect=True,
232                label=bui.Lstr(resource=self._r + '.configureKeyboardText'),
233                on_activate_call=self._config_keyboard,
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=self._r + '.configureKeyboard2Text'),
256                on_activate_call=self._config_keyboard2,
257            )
258            v -= spacing
259        if show_space_2:
260            v -= space_height
261        if show_remote:
262            self._idevices_button = btn = bui.buttonwidget(
263                parent=self._root_widget,
264                position=((width - button_width) / 2 - 5, v),
265                size=(button_width, 43),
266                autoselect=True,
267                label=bui.Lstr(resource=self._r + '.configureMobileText'),
268                on_activate_call=self._do_mobile_devices,
269            )
270            if bui.app.ui_v1.use_toolbars:
271                bui.widget(
272                    edit=btn,
273                    right_widget=bui.get_special_widget('party_button'),
274                )
275            if not self._have_selected_child:
276                bui.containerwidget(
277                    edit=self._root_widget, selected_child=self._idevices_button
278                )
279                bui.widget(
280                    edit=self._back_button, down_widget=self._idevices_button
281                )
282                self._have_selected_child = True
283            v -= spacing
284
285        if show_xinput_toggle:
286
287            def do_toggle(value: bool) -> None:
288                bui.screenmessage(
289                    bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
290                    color=(1, 1, 0),
291                )
292                bui.getsound('gunCocking').play()
293                bui.set_low_level_config_value('enablexinput', not value)
294
295            bui.checkboxwidget(
296                parent=self._root_widget,
297                position=(100, v + 3),
298                size=(120, 30),
299                value=(not bui.get_low_level_config_value('enablexinput', 1)),
300                maxwidth=200,
301                on_value_change_call=do_toggle,
302                text=bui.Lstr(resource='disableXInputText'),
303                autoselect=True,
304            )
305            bui.textwidget(
306                parent=self._root_widget,
307                position=(width * 0.5, v - 5),
308                size=(0, 0),
309                text=bui.Lstr(resource='disableXInputDescriptionText'),
310                scale=0.5,
311                h_align='center',
312                v_align='center',
313                color=bui.app.ui_v1.infotextcolor,
314                maxwidth=width * 0.8,
315            )
316            v -= spacing
317
318        if show_mac_controller_subsystem:
319            PopupMenu(
320                parent=self._root_widget,
321                position=(260, v - 10),
322                width=160,
323                button_size=(150, 50),
324                scale=1.5,
325                choices=['Classic', 'MFi', 'Both'],
326                choices_display=[
327                    bui.Lstr(resource='macControllerSubsystemClassicText'),
328                    bui.Lstr(resource='macControllerSubsystemMFiText'),
329                    bui.Lstr(resource='macControllerSubsystemBothText'),
330                ],
331                current_choice=bui.app.config.resolve(
332                    'Mac Controller Subsystem'
333                ),
334                on_value_change_call=self._set_mac_controller_subsystem,
335            )
336            bui.textwidget(
337                parent=self._root_widget,
338                position=(245, v + 13),
339                size=(0, 0),
340                text=bui.Lstr(resource='macControllerSubsystemTitleText'),
341                scale=1.0,
342                h_align='right',
343                v_align='center',
344                color=bui.app.ui_v1.infotextcolor,
345                maxwidth=180,
346            )
347            bui.textwidget(
348                parent=self._root_widget,
349                position=(width * 0.5, v - 20),
350                size=(0, 0),
351                text=bui.Lstr(resource='macControllerSubsystemDescriptionText'),
352                scale=0.5,
353                h_align='center',
354                v_align='center',
355                color=bui.app.ui_v1.infotextcolor,
356                maxwidth=width * 0.8,
357            )
358            v -= spacing * 1.5
359
360        self._restore_state()
Inherited Members
bauiv1._uitypes.Window
get_root_widget