bauiv1lib.settings.controls

Provides a top level control settings window.

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

Top level control settings window.

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

Create a MainWindow given a root widget and transition info.

Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it.

@override
def get_main_window_state(self) -> bauiv1.MainWindowState:
334    @override
335    def get_main_window_state(self) -> bui.MainWindowState:
336        # Support recreating our window for back/refresh purposes.
337        cls = type(self)
338        return bui.BasicMainWindowState(
339            create_call=lambda transition, origin_widget: cls(
340                transition=transition, origin_widget=origin_widget
341            )
342        )

Return a WindowState to recreate this window, if supported.

@override
def on_main_window_close(self) -> None:
344    @override
345    def on_main_window_close(self) -> None:
346        self._save_state()

Called before transitioning out a main window.

A good opportunity to save window state/etc.