bauiv1lib.settings.allsettings

UI for top level settings categories.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""UI for top level settings categories."""
  4
  5from __future__ import annotations
  6
  7from typing import TYPE_CHECKING, override
  8import logging
  9
 10import bauiv1 as bui
 11
 12if TYPE_CHECKING:
 13    from typing import Callable
 14
 15
 16class AllSettingsWindow(bui.MainWindow):
 17    """Window for selecting a settings category."""
 18
 19    def __init__(
 20        self,
 21        transition: str | None = 'in_right',
 22        origin_widget: bui.Widget | None = None,
 23    ):
 24        # pylint: disable=too-many-locals
 25
 26        # Preload some modules we use in a background thread so we won't
 27        # have a visual hitch when the user taps them.
 28        bui.app.threadpool.submit_no_wait(self._preload_modules)
 29
 30        bui.set_analytics_screen('Settings Window')
 31        assert bui.app.classic is not None
 32        uiscale = bui.app.ui_v1.uiscale
 33        width = 1000 if uiscale is bui.UIScale.SMALL else 900
 34        height = 800 if uiscale is bui.UIScale.SMALL else 450
 35        self._r = 'settingsWindow'
 36
 37        uiscale = bui.app.ui_v1.uiscale
 38
 39        # Do some fancy math to fill all available screen area up to the
 40        # size of our backing container. This lets us fit to the exact
 41        # screen shape at small ui scale.
 42        screensize = bui.get_virtual_screen_size()
 43        safesize = bui.get_virtual_safe_area_size()
 44
 45        # We're a generally widescreen shaped window, so bump our
 46        # overall scale up a bit when screen width is wider than safe
 47        # bounds to take advantage of the extra space.
 48        smallscale = min(2.0, 1.5 * screensize[0] / safesize[0])
 49
 50        scale = (
 51            smallscale
 52            if uiscale is bui.UIScale.SMALL
 53            else 1.1 if uiscale is bui.UIScale.MEDIUM else 0.8
 54        )
 55        # Calc screen size in our local container space and clamp to a
 56        # bit smaller than our container size.
 57        target_height = min(height - 70, screensize[1] / scale)
 58
 59        # To get top/left coords, go to the center of our window and
 60        # offset by half the width/height of our target area.
 61        yoffs = 0.5 * height + 0.5 * target_height + 30.0
 62
 63        # scroll_width = target_width
 64        # scroll_height = target_height - 25
 65        # scroll_bottom = yoffs - 54 - scroll_height
 66
 67        super().__init__(
 68            root_widget=bui.containerwidget(
 69                size=(width, height),
 70                toolbar_visibility=(
 71                    'menu_minimal'
 72                    if uiscale is bui.UIScale.SMALL
 73                    else 'menu_full'
 74                ),
 75                scale=scale,
 76            ),
 77            transition=transition,
 78            origin_widget=origin_widget,
 79            # We're affected by screen size only at small ui-scale.
 80            refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
 81        )
 82
 83        if uiscale is bui.UIScale.SMALL:
 84            self._back_button = None
 85            bui.containerwidget(
 86                edit=self._root_widget, on_cancel_call=self.main_window_back
 87            )
 88        else:
 89            self._back_button = btn = bui.buttonwidget(
 90                parent=self._root_widget,
 91                autoselect=True,
 92                position=(50, yoffs - 80.0),
 93                size=(70, 70),
 94                scale=0.8,
 95                text_scale=1.2,
 96                label=bui.charstr(bui.SpecialChar.BACK),
 97                button_type='backSmall',
 98                on_activate_call=self.main_window_back,
 99            )
100            bui.containerwidget(edit=self._root_widget, cancel_button=btn)
101
102        bui.textwidget(
103            parent=self._root_widget,
104            position=(0, yoffs - (70 if uiscale is bui.UIScale.SMALL else 60)),
105            size=(width, 25),
106            text=bui.Lstr(resource=f'{self._r}.titleText'),
107            color=bui.app.ui_v1.title_color,
108            h_align='center',
109            v_align='center',
110            scale=1.1,
111            maxwidth=130,
112        )
113
114        bwidth = 200
115        bheight = 230
116        margin = 1
117        all_buttons_width = 4.0 * bwidth + 3.0 * margin
118
119        x = width * 0.5 - all_buttons_width * 0.5
120        y = height * 0.5 - bheight * 0.5 - 20.0
121
122        def _button(
123            position: tuple[float, float],
124            label: bui.Lstr,
125            call: Callable[[], None],
126            texture: bui.Texture,
127            imgsize: float,
128            *,
129            color: tuple[float, float, float] = (1.0, 1.0, 1.0),
130            imgoffs: tuple[float, float] = (0.0, 0.0),
131        ) -> bui.Widget:
132            x, y = position
133            btn = bui.buttonwidget(
134                parent=self._root_widget,
135                autoselect=True,
136                position=(x, y),
137                size=(bwidth, bheight),
138                button_type='square',
139                label='',
140                on_activate_call=call,
141            )
142            bui.textwidget(
143                parent=self._root_widget,
144                text=label,
145                position=(x + bwidth * 0.5, y + bheight * 0.25),
146                maxwidth=bwidth * 0.7,
147                size=(0, 0),
148                h_align='center',
149                v_align='center',
150                draw_controller=btn,
151                color=(0.7, 0.9, 0.7, 1.0),
152            )
153            bui.imagewidget(
154                parent=self._root_widget,
155                position=(
156                    x + bwidth * 0.5 - imgsize * 0.5 + imgoffs[0],
157                    y + bheight * 0.56 - imgsize * 0.5 + imgoffs[1],
158                ),
159                size=(imgsize, imgsize),
160                texture=texture,
161                draw_controller=btn,
162                color=color,
163            )
164            return btn
165
166        self._controllers_button = _button(
167            position=(x, y),
168            label=bui.Lstr(resource=f'{self._r}.controllersText'),
169            call=self._do_controllers,
170            texture=bui.gettexture('controllerIcon'),
171            imgsize=150,
172            imgoffs=(-2.0, 2.0),
173        )
174        x += bwidth + margin
175
176        self._graphics_button = _button(
177            position=(x, y),
178            label=bui.Lstr(resource=f'{self._r}.graphicsText'),
179            call=self._do_graphics,
180            texture=bui.gettexture('graphicsIcon'),
181            imgsize=135,
182            imgoffs=(0, 4.0),
183        )
184        x += bwidth + margin
185
186        self._audio_button = _button(
187            position=(x, y),
188            label=bui.Lstr(resource=f'{self._r}.audioText'),
189            call=self._do_audio,
190            texture=bui.gettexture('audioIcon'),
191            imgsize=150,
192            color=(1, 1, 0),
193        )
194        x += bwidth + margin
195
196        self._advanced_button = _button(
197            position=(x, y),
198            label=bui.Lstr(resource=f'{self._r}.advancedText'),
199            call=self._do_advanced,
200            texture=bui.gettexture('advancedIcon'),
201            imgsize=150,
202            color=(0.8, 0.95, 1),
203            imgoffs=(0, 5.0),
204        )
205
206        # Hmm; we're now wide enough that being limited to pressing up
207        # might be ok.
208        if bool(False):
209            # Left from our leftmost button should go to back button.
210            if self._back_button is None:
211                bbtn = bui.get_special_widget('back_button')
212                bui.widget(edit=self._controllers_button, left_widget=bbtn)
213
214            # Right from our rightmost widget should go to squad button.
215            bui.widget(
216                edit=self._advanced_button,
217                right_widget=bui.get_special_widget('squad_button'),
218            )
219
220        self._restore_state()
221
222    @override
223    def get_main_window_state(self) -> bui.MainWindowState:
224        # Support recreating our window for back/refresh purposes.
225        cls = type(self)
226        return bui.BasicMainWindowState(
227            create_call=lambda transition, origin_widget: cls(
228                transition=transition, origin_widget=origin_widget
229            )
230        )
231
232    @override
233    def on_main_window_close(self) -> None:
234        self._save_state()
235
236    @staticmethod
237    def _preload_modules() -> None:
238        """Preload modules we use; avoids hitches (called in bg thread)."""
239        import bauiv1lib.mainmenu as _unused1
240        import bauiv1lib.settings.controls as _unused2
241        import bauiv1lib.settings.graphics as _unused3
242        import bauiv1lib.settings.audio as _unused4
243        import bauiv1lib.settings.advanced as _unused5
244
245    def _do_controllers(self) -> None:
246        # pylint: disable=cyclic-import
247        from bauiv1lib.settings.controls import ControlsSettingsWindow
248
249        # no-op if we're not in control.
250        if not self.main_window_has_control():
251            return
252
253        self.main_window_replace(
254            ControlsSettingsWindow(origin_widget=self._controllers_button)
255        )
256
257    def _do_graphics(self) -> None:
258        # pylint: disable=cyclic-import
259        from bauiv1lib.settings.graphics import GraphicsSettingsWindow
260
261        # no-op if we're not in control.
262        if not self.main_window_has_control():
263            return
264
265        self.main_window_replace(
266            GraphicsSettingsWindow(origin_widget=self._graphics_button)
267        )
268
269    def _do_audio(self) -> None:
270        # pylint: disable=cyclic-import
271        from bauiv1lib.settings.audio import AudioSettingsWindow
272
273        # no-op if we're not in control.
274        if not self.main_window_has_control():
275            return
276
277        self.main_window_replace(
278            AudioSettingsWindow(origin_widget=self._audio_button)
279        )
280
281    def _do_advanced(self) -> None:
282        # pylint: disable=cyclic-import
283        from bauiv1lib.settings.advanced import AdvancedSettingsWindow
284
285        # no-op if we're not in control.
286        if not self.main_window_has_control():
287            return
288
289        self.main_window_replace(
290            AdvancedSettingsWindow(origin_widget=self._advanced_button)
291        )
292
293    def _save_state(self) -> None:
294        try:
295            sel = self._root_widget.get_selected_child()
296            if sel == self._controllers_button:
297                sel_name = 'Controllers'
298            elif sel == self._graphics_button:
299                sel_name = 'Graphics'
300            elif sel == self._audio_button:
301                sel_name = 'Audio'
302            elif sel == self._advanced_button:
303                sel_name = 'Advanced'
304            elif sel == self._back_button:
305                sel_name = 'Back'
306            else:
307                raise ValueError(f'unrecognized selection \'{sel}\'')
308            assert bui.app.classic is not None
309            bui.app.ui_v1.window_states[type(self)] = {'sel_name': sel_name}
310        except Exception:
311            logging.exception('Error saving state for %s.', self)
312
313    def _restore_state(self) -> None:
314        try:
315            assert bui.app.classic is not None
316            sel_name = bui.app.ui_v1.window_states.get(type(self), {}).get(
317                'sel_name'
318            )
319            sel: bui.Widget | None
320            if sel_name == 'Controllers':
321                sel = self._controllers_button
322            elif sel_name == 'Graphics':
323                sel = self._graphics_button
324            elif sel_name == 'Audio':
325                sel = self._audio_button
326            elif sel_name == 'Advanced':
327                sel = self._advanced_button
328            elif sel_name == 'Back':
329                sel = self._back_button
330            else:
331                sel = self._controllers_button
332            if sel is not None:
333                bui.containerwidget(edit=self._root_widget, selected_child=sel)
334        except Exception:
335            logging.exception('Error restoring state for %s.', self)
class AllSettingsWindow(bauiv1._uitypes.MainWindow):
 17class AllSettingsWindow(bui.MainWindow):
 18    """Window for selecting a settings category."""
 19
 20    def __init__(
 21        self,
 22        transition: str | None = 'in_right',
 23        origin_widget: bui.Widget | None = None,
 24    ):
 25        # pylint: disable=too-many-locals
 26
 27        # Preload some modules we use in a background thread so we won't
 28        # have a visual hitch when the user taps them.
 29        bui.app.threadpool.submit_no_wait(self._preload_modules)
 30
 31        bui.set_analytics_screen('Settings Window')
 32        assert bui.app.classic is not None
 33        uiscale = bui.app.ui_v1.uiscale
 34        width = 1000 if uiscale is bui.UIScale.SMALL else 900
 35        height = 800 if uiscale is bui.UIScale.SMALL else 450
 36        self._r = 'settingsWindow'
 37
 38        uiscale = bui.app.ui_v1.uiscale
 39
 40        # Do some fancy math to fill all available screen area up to the
 41        # size of our backing container. This lets us fit to the exact
 42        # screen shape at small ui scale.
 43        screensize = bui.get_virtual_screen_size()
 44        safesize = bui.get_virtual_safe_area_size()
 45
 46        # We're a generally widescreen shaped window, so bump our
 47        # overall scale up a bit when screen width is wider than safe
 48        # bounds to take advantage of the extra space.
 49        smallscale = min(2.0, 1.5 * screensize[0] / safesize[0])
 50
 51        scale = (
 52            smallscale
 53            if uiscale is bui.UIScale.SMALL
 54            else 1.1 if uiscale is bui.UIScale.MEDIUM else 0.8
 55        )
 56        # Calc screen size in our local container space and clamp to a
 57        # bit smaller than our container size.
 58        target_height = min(height - 70, screensize[1] / scale)
 59
 60        # To get top/left coords, go to the center of our window and
 61        # offset by half the width/height of our target area.
 62        yoffs = 0.5 * height + 0.5 * target_height + 30.0
 63
 64        # scroll_width = target_width
 65        # scroll_height = target_height - 25
 66        # scroll_bottom = yoffs - 54 - scroll_height
 67
 68        super().__init__(
 69            root_widget=bui.containerwidget(
 70                size=(width, height),
 71                toolbar_visibility=(
 72                    'menu_minimal'
 73                    if uiscale is bui.UIScale.SMALL
 74                    else 'menu_full'
 75                ),
 76                scale=scale,
 77            ),
 78            transition=transition,
 79            origin_widget=origin_widget,
 80            # We're affected by screen size only at small ui-scale.
 81            refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
 82        )
 83
 84        if uiscale is bui.UIScale.SMALL:
 85            self._back_button = None
 86            bui.containerwidget(
 87                edit=self._root_widget, on_cancel_call=self.main_window_back
 88            )
 89        else:
 90            self._back_button = btn = bui.buttonwidget(
 91                parent=self._root_widget,
 92                autoselect=True,
 93                position=(50, yoffs - 80.0),
 94                size=(70, 70),
 95                scale=0.8,
 96                text_scale=1.2,
 97                label=bui.charstr(bui.SpecialChar.BACK),
 98                button_type='backSmall',
 99                on_activate_call=self.main_window_back,
100            )
101            bui.containerwidget(edit=self._root_widget, cancel_button=btn)
102
103        bui.textwidget(
104            parent=self._root_widget,
105            position=(0, yoffs - (70 if uiscale is bui.UIScale.SMALL else 60)),
106            size=(width, 25),
107            text=bui.Lstr(resource=f'{self._r}.titleText'),
108            color=bui.app.ui_v1.title_color,
109            h_align='center',
110            v_align='center',
111            scale=1.1,
112            maxwidth=130,
113        )
114
115        bwidth = 200
116        bheight = 230
117        margin = 1
118        all_buttons_width = 4.0 * bwidth + 3.0 * margin
119
120        x = width * 0.5 - all_buttons_width * 0.5
121        y = height * 0.5 - bheight * 0.5 - 20.0
122
123        def _button(
124            position: tuple[float, float],
125            label: bui.Lstr,
126            call: Callable[[], None],
127            texture: bui.Texture,
128            imgsize: float,
129            *,
130            color: tuple[float, float, float] = (1.0, 1.0, 1.0),
131            imgoffs: tuple[float, float] = (0.0, 0.0),
132        ) -> bui.Widget:
133            x, y = position
134            btn = bui.buttonwidget(
135                parent=self._root_widget,
136                autoselect=True,
137                position=(x, y),
138                size=(bwidth, bheight),
139                button_type='square',
140                label='',
141                on_activate_call=call,
142            )
143            bui.textwidget(
144                parent=self._root_widget,
145                text=label,
146                position=(x + bwidth * 0.5, y + bheight * 0.25),
147                maxwidth=bwidth * 0.7,
148                size=(0, 0),
149                h_align='center',
150                v_align='center',
151                draw_controller=btn,
152                color=(0.7, 0.9, 0.7, 1.0),
153            )
154            bui.imagewidget(
155                parent=self._root_widget,
156                position=(
157                    x + bwidth * 0.5 - imgsize * 0.5 + imgoffs[0],
158                    y + bheight * 0.56 - imgsize * 0.5 + imgoffs[1],
159                ),
160                size=(imgsize, imgsize),
161                texture=texture,
162                draw_controller=btn,
163                color=color,
164            )
165            return btn
166
167        self._controllers_button = _button(
168            position=(x, y),
169            label=bui.Lstr(resource=f'{self._r}.controllersText'),
170            call=self._do_controllers,
171            texture=bui.gettexture('controllerIcon'),
172            imgsize=150,
173            imgoffs=(-2.0, 2.0),
174        )
175        x += bwidth + margin
176
177        self._graphics_button = _button(
178            position=(x, y),
179            label=bui.Lstr(resource=f'{self._r}.graphicsText'),
180            call=self._do_graphics,
181            texture=bui.gettexture('graphicsIcon'),
182            imgsize=135,
183            imgoffs=(0, 4.0),
184        )
185        x += bwidth + margin
186
187        self._audio_button = _button(
188            position=(x, y),
189            label=bui.Lstr(resource=f'{self._r}.audioText'),
190            call=self._do_audio,
191            texture=bui.gettexture('audioIcon'),
192            imgsize=150,
193            color=(1, 1, 0),
194        )
195        x += bwidth + margin
196
197        self._advanced_button = _button(
198            position=(x, y),
199            label=bui.Lstr(resource=f'{self._r}.advancedText'),
200            call=self._do_advanced,
201            texture=bui.gettexture('advancedIcon'),
202            imgsize=150,
203            color=(0.8, 0.95, 1),
204            imgoffs=(0, 5.0),
205        )
206
207        # Hmm; we're now wide enough that being limited to pressing up
208        # might be ok.
209        if bool(False):
210            # Left from our leftmost button should go to back button.
211            if self._back_button is None:
212                bbtn = bui.get_special_widget('back_button')
213                bui.widget(edit=self._controllers_button, left_widget=bbtn)
214
215            # Right from our rightmost widget should go to squad button.
216            bui.widget(
217                edit=self._advanced_button,
218                right_widget=bui.get_special_widget('squad_button'),
219            )
220
221        self._restore_state()
222
223    @override
224    def get_main_window_state(self) -> bui.MainWindowState:
225        # Support recreating our window for back/refresh purposes.
226        cls = type(self)
227        return bui.BasicMainWindowState(
228            create_call=lambda transition, origin_widget: cls(
229                transition=transition, origin_widget=origin_widget
230            )
231        )
232
233    @override
234    def on_main_window_close(self) -> None:
235        self._save_state()
236
237    @staticmethod
238    def _preload_modules() -> None:
239        """Preload modules we use; avoids hitches (called in bg thread)."""
240        import bauiv1lib.mainmenu as _unused1
241        import bauiv1lib.settings.controls as _unused2
242        import bauiv1lib.settings.graphics as _unused3
243        import bauiv1lib.settings.audio as _unused4
244        import bauiv1lib.settings.advanced as _unused5
245
246    def _do_controllers(self) -> None:
247        # pylint: disable=cyclic-import
248        from bauiv1lib.settings.controls import ControlsSettingsWindow
249
250        # no-op if we're not in control.
251        if not self.main_window_has_control():
252            return
253
254        self.main_window_replace(
255            ControlsSettingsWindow(origin_widget=self._controllers_button)
256        )
257
258    def _do_graphics(self) -> None:
259        # pylint: disable=cyclic-import
260        from bauiv1lib.settings.graphics import GraphicsSettingsWindow
261
262        # no-op if we're not in control.
263        if not self.main_window_has_control():
264            return
265
266        self.main_window_replace(
267            GraphicsSettingsWindow(origin_widget=self._graphics_button)
268        )
269
270    def _do_audio(self) -> None:
271        # pylint: disable=cyclic-import
272        from bauiv1lib.settings.audio import AudioSettingsWindow
273
274        # no-op if we're not in control.
275        if not self.main_window_has_control():
276            return
277
278        self.main_window_replace(
279            AudioSettingsWindow(origin_widget=self._audio_button)
280        )
281
282    def _do_advanced(self) -> None:
283        # pylint: disable=cyclic-import
284        from bauiv1lib.settings.advanced import AdvancedSettingsWindow
285
286        # no-op if we're not in control.
287        if not self.main_window_has_control():
288            return
289
290        self.main_window_replace(
291            AdvancedSettingsWindow(origin_widget=self._advanced_button)
292        )
293
294    def _save_state(self) -> None:
295        try:
296            sel = self._root_widget.get_selected_child()
297            if sel == self._controllers_button:
298                sel_name = 'Controllers'
299            elif sel == self._graphics_button:
300                sel_name = 'Graphics'
301            elif sel == self._audio_button:
302                sel_name = 'Audio'
303            elif sel == self._advanced_button:
304                sel_name = 'Advanced'
305            elif sel == self._back_button:
306                sel_name = 'Back'
307            else:
308                raise ValueError(f'unrecognized selection \'{sel}\'')
309            assert bui.app.classic is not None
310            bui.app.ui_v1.window_states[type(self)] = {'sel_name': sel_name}
311        except Exception:
312            logging.exception('Error saving state for %s.', self)
313
314    def _restore_state(self) -> None:
315        try:
316            assert bui.app.classic is not None
317            sel_name = bui.app.ui_v1.window_states.get(type(self), {}).get(
318                'sel_name'
319            )
320            sel: bui.Widget | None
321            if sel_name == 'Controllers':
322                sel = self._controllers_button
323            elif sel_name == 'Graphics':
324                sel = self._graphics_button
325            elif sel_name == 'Audio':
326                sel = self._audio_button
327            elif sel_name == 'Advanced':
328                sel = self._advanced_button
329            elif sel_name == 'Back':
330                sel = self._back_button
331            else:
332                sel = self._controllers_button
333            if sel is not None:
334                bui.containerwidget(edit=self._root_widget, selected_child=sel)
335        except Exception:
336            logging.exception('Error restoring state for %s.', self)

Window for selecting a settings category.

AllSettingsWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
 20    def __init__(
 21        self,
 22        transition: str | None = 'in_right',
 23        origin_widget: bui.Widget | None = None,
 24    ):
 25        # pylint: disable=too-many-locals
 26
 27        # Preload some modules we use in a background thread so we won't
 28        # have a visual hitch when the user taps them.
 29        bui.app.threadpool.submit_no_wait(self._preload_modules)
 30
 31        bui.set_analytics_screen('Settings Window')
 32        assert bui.app.classic is not None
 33        uiscale = bui.app.ui_v1.uiscale
 34        width = 1000 if uiscale is bui.UIScale.SMALL else 900
 35        height = 800 if uiscale is bui.UIScale.SMALL else 450
 36        self._r = 'settingsWindow'
 37
 38        uiscale = bui.app.ui_v1.uiscale
 39
 40        # Do some fancy math to fill all available screen area up to the
 41        # size of our backing container. This lets us fit to the exact
 42        # screen shape at small ui scale.
 43        screensize = bui.get_virtual_screen_size()
 44        safesize = bui.get_virtual_safe_area_size()
 45
 46        # We're a generally widescreen shaped window, so bump our
 47        # overall scale up a bit when screen width is wider than safe
 48        # bounds to take advantage of the extra space.
 49        smallscale = min(2.0, 1.5 * screensize[0] / safesize[0])
 50
 51        scale = (
 52            smallscale
 53            if uiscale is bui.UIScale.SMALL
 54            else 1.1 if uiscale is bui.UIScale.MEDIUM else 0.8
 55        )
 56        # Calc screen size in our local container space and clamp to a
 57        # bit smaller than our container size.
 58        target_height = min(height - 70, screensize[1] / scale)
 59
 60        # To get top/left coords, go to the center of our window and
 61        # offset by half the width/height of our target area.
 62        yoffs = 0.5 * height + 0.5 * target_height + 30.0
 63
 64        # scroll_width = target_width
 65        # scroll_height = target_height - 25
 66        # scroll_bottom = yoffs - 54 - scroll_height
 67
 68        super().__init__(
 69            root_widget=bui.containerwidget(
 70                size=(width, height),
 71                toolbar_visibility=(
 72                    'menu_minimal'
 73                    if uiscale is bui.UIScale.SMALL
 74                    else 'menu_full'
 75                ),
 76                scale=scale,
 77            ),
 78            transition=transition,
 79            origin_widget=origin_widget,
 80            # We're affected by screen size only at small ui-scale.
 81            refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
 82        )
 83
 84        if uiscale is bui.UIScale.SMALL:
 85            self._back_button = None
 86            bui.containerwidget(
 87                edit=self._root_widget, on_cancel_call=self.main_window_back
 88            )
 89        else:
 90            self._back_button = btn = bui.buttonwidget(
 91                parent=self._root_widget,
 92                autoselect=True,
 93                position=(50, yoffs - 80.0),
 94                size=(70, 70),
 95                scale=0.8,
 96                text_scale=1.2,
 97                label=bui.charstr(bui.SpecialChar.BACK),
 98                button_type='backSmall',
 99                on_activate_call=self.main_window_back,
100            )
101            bui.containerwidget(edit=self._root_widget, cancel_button=btn)
102
103        bui.textwidget(
104            parent=self._root_widget,
105            position=(0, yoffs - (70 if uiscale is bui.UIScale.SMALL else 60)),
106            size=(width, 25),
107            text=bui.Lstr(resource=f'{self._r}.titleText'),
108            color=bui.app.ui_v1.title_color,
109            h_align='center',
110            v_align='center',
111            scale=1.1,
112            maxwidth=130,
113        )
114
115        bwidth = 200
116        bheight = 230
117        margin = 1
118        all_buttons_width = 4.0 * bwidth + 3.0 * margin
119
120        x = width * 0.5 - all_buttons_width * 0.5
121        y = height * 0.5 - bheight * 0.5 - 20.0
122
123        def _button(
124            position: tuple[float, float],
125            label: bui.Lstr,
126            call: Callable[[], None],
127            texture: bui.Texture,
128            imgsize: float,
129            *,
130            color: tuple[float, float, float] = (1.0, 1.0, 1.0),
131            imgoffs: tuple[float, float] = (0.0, 0.0),
132        ) -> bui.Widget:
133            x, y = position
134            btn = bui.buttonwidget(
135                parent=self._root_widget,
136                autoselect=True,
137                position=(x, y),
138                size=(bwidth, bheight),
139                button_type='square',
140                label='',
141                on_activate_call=call,
142            )
143            bui.textwidget(
144                parent=self._root_widget,
145                text=label,
146                position=(x + bwidth * 0.5, y + bheight * 0.25),
147                maxwidth=bwidth * 0.7,
148                size=(0, 0),
149                h_align='center',
150                v_align='center',
151                draw_controller=btn,
152                color=(0.7, 0.9, 0.7, 1.0),
153            )
154            bui.imagewidget(
155                parent=self._root_widget,
156                position=(
157                    x + bwidth * 0.5 - imgsize * 0.5 + imgoffs[0],
158                    y + bheight * 0.56 - imgsize * 0.5 + imgoffs[1],
159                ),
160                size=(imgsize, imgsize),
161                texture=texture,
162                draw_controller=btn,
163                color=color,
164            )
165            return btn
166
167        self._controllers_button = _button(
168            position=(x, y),
169            label=bui.Lstr(resource=f'{self._r}.controllersText'),
170            call=self._do_controllers,
171            texture=bui.gettexture('controllerIcon'),
172            imgsize=150,
173            imgoffs=(-2.0, 2.0),
174        )
175        x += bwidth + margin
176
177        self._graphics_button = _button(
178            position=(x, y),
179            label=bui.Lstr(resource=f'{self._r}.graphicsText'),
180            call=self._do_graphics,
181            texture=bui.gettexture('graphicsIcon'),
182            imgsize=135,
183            imgoffs=(0, 4.0),
184        )
185        x += bwidth + margin
186
187        self._audio_button = _button(
188            position=(x, y),
189            label=bui.Lstr(resource=f'{self._r}.audioText'),
190            call=self._do_audio,
191            texture=bui.gettexture('audioIcon'),
192            imgsize=150,
193            color=(1, 1, 0),
194        )
195        x += bwidth + margin
196
197        self._advanced_button = _button(
198            position=(x, y),
199            label=bui.Lstr(resource=f'{self._r}.advancedText'),
200            call=self._do_advanced,
201            texture=bui.gettexture('advancedIcon'),
202            imgsize=150,
203            color=(0.8, 0.95, 1),
204            imgoffs=(0, 5.0),
205        )
206
207        # Hmm; we're now wide enough that being limited to pressing up
208        # might be ok.
209        if bool(False):
210            # Left from our leftmost button should go to back button.
211            if self._back_button is None:
212                bbtn = bui.get_special_widget('back_button')
213                bui.widget(edit=self._controllers_button, left_widget=bbtn)
214
215            # Right from our rightmost widget should go to squad button.
216            bui.widget(
217                edit=self._advanced_button,
218                right_widget=bui.get_special_widget('squad_button'),
219            )
220
221        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:
223    @override
224    def get_main_window_state(self) -> bui.MainWindowState:
225        # Support recreating our window for back/refresh purposes.
226        cls = type(self)
227        return bui.BasicMainWindowState(
228            create_call=lambda transition, origin_widget: cls(
229                transition=transition, origin_widget=origin_widget
230            )
231        )

Return a WindowState to recreate this window, if supported.

@override
def on_main_window_close(self) -> None:
233    @override
234    def on_main_window_close(self) -> None:
235        self._save_state()

Called before transitioning out a main window.

A good opportunity to save window state/etc.