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

Return a WindowState to recreate this window, if supported.

@override
def on_main_window_close(self) -> None:
356    @override
357    def on_main_window_close(self) -> None:
358        self._save_state()

Called before transitioning out a main window.

A good opportunity to save window state/etc.