bauiv1lib.soundtrack.browser

Provides UI for browsing soundtracks.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides UI for browsing soundtracks."""
  4
  5from __future__ import annotations
  6
  7import copy
  8import logging
  9from typing import TYPE_CHECKING, override
 10
 11import bauiv1 as bui
 12
 13if TYPE_CHECKING:
 14    from typing import Any
 15
 16REQUIRE_PRO = False
 17
 18# Temp.
 19UNDER_CONSTRUCTION = True
 20
 21
 22class SoundtrackBrowserWindow(bui.MainWindow):
 23    """Window for browsing soundtracks."""
 24
 25    def __init__(
 26        self,
 27        transition: str | None = 'in_right',
 28        origin_widget: bui.Widget | None = None,
 29    ):
 30        # pylint: disable=too-many-statements
 31
 32        self._r = 'editSoundtrackWindow'
 33        assert bui.app.classic is not None
 34        uiscale = bui.app.ui_v1.uiscale
 35        self._width = 800 if uiscale is bui.UIScale.SMALL else 600
 36        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 37        self._height = (
 38            340
 39            if uiscale is bui.UIScale.SMALL
 40            else 370 if uiscale is bui.UIScale.MEDIUM else 440
 41        )
 42        spacing = 40.0
 43        v = self._height - 40.0
 44        v -= spacing * 1.0
 45
 46        super().__init__(
 47            root_widget=bui.containerwidget(
 48                size=(self._width, self._height),
 49                toolbar_visibility=(
 50                    'menu_minimal'
 51                    if uiscale is bui.UIScale.SMALL
 52                    else 'menu_full'
 53                ),
 54                scale=(
 55                    2.1
 56                    if uiscale is bui.UIScale.SMALL
 57                    else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0
 58                ),
 59                stack_offset=(
 60                    (0, -18) if uiscale is bui.UIScale.SMALL else (0, 0)
 61                ),
 62            ),
 63            transition=transition,
 64            origin_widget=origin_widget,
 65        )
 66
 67        assert bui.app.classic is not None
 68        if uiscale is bui.UIScale.SMALL:
 69            self._back_button = None
 70        else:
 71            self._back_button = bui.buttonwidget(
 72                parent=self._root_widget,
 73                position=(45 + x_inset, self._height - 60),
 74                size=(120, 60),
 75                scale=0.8,
 76                label=bui.Lstr(resource='backText'),
 77                button_type='back',
 78                autoselect=True,
 79            )
 80            bui.buttonwidget(
 81                edit=self._back_button,
 82                button_type='backSmall',
 83                size=(60, 60),
 84                label=bui.charstr(bui.SpecialChar.BACK),
 85            )
 86        bui.textwidget(
 87            parent=self._root_widget,
 88            position=(self._width * 0.5, self._height - 35),
 89            size=(0, 0),
 90            maxwidth=300,
 91            text=bui.Lstr(resource=f'{self._r}.titleText'),
 92            color=bui.app.ui_v1.title_color,
 93            h_align='center',
 94            v_align='center',
 95        )
 96
 97        h = 43 + x_inset
 98        v = self._height - 60
 99        b_color = (0.6, 0.53, 0.63)
100        b_textcolor = (0.75, 0.7, 0.8)
101        lock_tex = bui.gettexture('lock')
102        self._lock_images: list[bui.Widget] = []
103
104        scl = (
105            1.0
106            if uiscale is bui.UIScale.SMALL
107            else 1.13 if uiscale is bui.UIScale.MEDIUM else 1.4
108        )
109        v -= 60.0 * scl
110        self._new_button = btn = bui.buttonwidget(
111            parent=self._root_widget,
112            position=(h, v),
113            size=(100, 55.0 * scl),
114            on_activate_call=self._new_soundtrack,
115            color=b_color,
116            button_type='square',
117            autoselect=True,
118            textcolor=b_textcolor,
119            text_scale=0.7,
120            label=bui.Lstr(resource=f'{self._r}.newText'),
121        )
122        self._lock_images.append(
123            bui.imagewidget(
124                parent=self._root_widget,
125                size=(30, 30),
126                draw_controller=btn,
127                position=(h - 10, v + 55.0 * scl - 28),
128                texture=lock_tex,
129            )
130        )
131
132        if self._back_button is None:
133            bui.widget(
134                edit=btn,
135                left_widget=bui.get_special_widget('back_button'),
136            )
137        v -= 60.0 * scl
138
139        self._edit_button = btn = bui.buttonwidget(
140            parent=self._root_widget,
141            position=(h, v),
142            size=(100, 55.0 * scl),
143            on_activate_call=self._edit_soundtrack,
144            color=b_color,
145            button_type='square',
146            autoselect=True,
147            textcolor=b_textcolor,
148            text_scale=0.7,
149            label=bui.Lstr(resource=f'{self._r}.editText'),
150        )
151        self._lock_images.append(
152            bui.imagewidget(
153                parent=self._root_widget,
154                size=(30, 30),
155                draw_controller=btn,
156                position=(h - 10, v + 55.0 * scl - 28),
157                texture=lock_tex,
158            )
159        )
160        if self._back_button is None:
161            bui.widget(
162                edit=btn,
163                left_widget=bui.get_special_widget('back_button'),
164            )
165        v -= 60.0 * scl
166
167        self._duplicate_button = btn = bui.buttonwidget(
168            parent=self._root_widget,
169            position=(h, v),
170            size=(100, 55.0 * scl),
171            on_activate_call=self._duplicate_soundtrack,
172            button_type='square',
173            autoselect=True,
174            color=b_color,
175            textcolor=b_textcolor,
176            text_scale=0.7,
177            label=bui.Lstr(resource=f'{self._r}.duplicateText'),
178        )
179        self._lock_images.append(
180            bui.imagewidget(
181                parent=self._root_widget,
182                size=(30, 30),
183                draw_controller=btn,
184                position=(h - 10, v + 55.0 * scl - 28),
185                texture=lock_tex,
186            )
187        )
188        if self._back_button is None:
189            bui.widget(
190                edit=btn,
191                left_widget=bui.get_special_widget('back_button'),
192            )
193        v -= 60.0 * scl
194
195        self._delete_button = btn = bui.buttonwidget(
196            parent=self._root_widget,
197            position=(h, v),
198            size=(100, 55.0 * scl),
199            on_activate_call=self._delete_soundtrack,
200            color=b_color,
201            button_type='square',
202            autoselect=True,
203            textcolor=b_textcolor,
204            text_scale=0.7,
205            label=bui.Lstr(resource=f'{self._r}.deleteText'),
206        )
207        self._lock_images.append(
208            bui.imagewidget(
209                parent=self._root_widget,
210                size=(30, 30),
211                draw_controller=btn,
212                position=(h - 10, v + 55.0 * scl - 28),
213                texture=lock_tex,
214            )
215        )
216        if self._back_button is None:
217            bui.widget(
218                edit=btn,
219                left_widget=bui.get_special_widget('back_button'),
220            )
221
222        # Keep our lock images up to date/etc.
223        self._update_timer = bui.AppTimer(
224            1.0, bui.WeakCall(self._update), repeat=True
225        )
226        self._update()
227
228        v = self._height - 65
229        scroll_height = self._height - 105
230        v -= scroll_height
231        self._scrollwidget = scrollwidget = bui.scrollwidget(
232            parent=self._root_widget,
233            position=(152 + x_inset, v),
234            highlight=False,
235            size=(self._width - (205 + 2 * x_inset), scroll_height),
236        )
237        bui.widget(
238            edit=self._scrollwidget,
239            left_widget=self._new_button,
240            right_widget=bui.get_special_widget('squad_button'),
241        )
242        self._col = bui.columnwidget(parent=scrollwidget, border=2, margin=0)
243
244        self._soundtracks: dict[str, Any] | None = None
245        self._selected_soundtrack: str | None = None
246        self._selected_soundtrack_index: int | None = None
247        self._soundtrack_widgets: list[bui.Widget] = []
248        self._allow_changing_soundtracks = False
249        self._refresh()
250        if self._back_button is not None:
251            bui.buttonwidget(
252                edit=self._back_button, on_activate_call=self.main_window_back
253            )
254            bui.containerwidget(
255                edit=self._root_widget, cancel_button=self._back_button
256            )
257        else:
258            bui.containerwidget(
259                edit=self._root_widget, on_cancel_call=self.main_window_back
260            )
261
262    @override
263    def get_main_window_state(self) -> bui.MainWindowState:
264        # Support recreating our window for back/refresh purposes.
265        cls = type(self)
266        return bui.BasicMainWindowState(
267            create_call=lambda transition, origin_widget: cls(
268                transition=transition, origin_widget=origin_widget
269            )
270        )
271
272    @override
273    def on_main_window_close(self) -> None:
274        self._save_state()
275
276    def _update(self) -> None:
277        have_pro = (
278            bui.app.classic is None
279            or bui.app.classic.accounts.have_pro_options()
280        )
281        for lock in self._lock_images:
282            bui.imagewidget(
283                edit=lock, opacity=0.0 if (have_pro or not REQUIRE_PRO) else 1.0
284            )
285
286    def _do_delete_soundtrack(self) -> None:
287        cfg = bui.app.config
288        soundtracks = cfg.setdefault('Soundtracks', {})
289        if self._selected_soundtrack in soundtracks:
290            del soundtracks[self._selected_soundtrack]
291        cfg.commit()
292        bui.getsound('shieldDown').play()
293        assert self._selected_soundtrack_index is not None
294        assert self._soundtracks is not None
295        self._selected_soundtrack_index = min(
296            self._selected_soundtrack_index, len(self._soundtracks)
297        )
298        self._refresh()
299
300    def _delete_soundtrack(self) -> None:
301        # pylint: disable=cyclic-import
302        from bauiv1lib.purchase import PurchaseWindow
303        from bauiv1lib.confirm import ConfirmWindow
304
305        if REQUIRE_PRO and (
306            bui.app.classic is not None
307            and not bui.app.classic.accounts.have_pro_options()
308        ):
309            PurchaseWindow(items=['pro'])
310            return
311        if self._selected_soundtrack is None:
312            return
313        if self._selected_soundtrack == '__default__':
314            bui.getsound('error').play()
315            bui.screenmessage(
316                bui.Lstr(resource=f'{self._r}.cantDeleteDefaultText'),
317                color=(1, 0, 0),
318            )
319        else:
320            ConfirmWindow(
321                bui.Lstr(
322                    resource=f'{self._r}.deleteConfirmText',
323                    subs=[('${NAME}', self._selected_soundtrack)],
324                ),
325                self._do_delete_soundtrack,
326                450,
327                150,
328            )
329
330    def _duplicate_soundtrack(self) -> None:
331        # pylint: disable=cyclic-import
332        from bauiv1lib.purchase import PurchaseWindow
333
334        if REQUIRE_PRO and (
335            bui.app.classic is not None
336            and not bui.app.classic.accounts.have_pro_options()
337        ):
338            PurchaseWindow(items=['pro'])
339            return
340        cfg = bui.app.config
341        cfg.setdefault('Soundtracks', {})
342
343        if self._selected_soundtrack is None:
344            return
345        sdtk: dict[str, Any]
346        if self._selected_soundtrack == '__default__':
347            sdtk = {}
348        else:
349            sdtk = cfg['Soundtracks'][self._selected_soundtrack]
350
351        # Find a valid dup name that doesn't exist.
352        test_index = 1
353        copy_text = bui.Lstr(resource='copyOfText').evaluate()
354        # Get just 'Copy' or whatnot.
355        copy_word = copy_text.replace('${NAME}', '').strip()
356        base_name = self._get_soundtrack_display_name(
357            self._selected_soundtrack
358        ).evaluate()
359        assert isinstance(base_name, str)
360
361        # If it looks like a copy, strip digits and spaces off the end.
362        if copy_word in base_name:
363            while base_name[-1].isdigit() or base_name[-1] == ' ':
364                base_name = base_name[:-1]
365        while True:
366            if copy_word in base_name:
367                test_name = base_name
368            else:
369                test_name = copy_text.replace('${NAME}', base_name)
370            if test_index > 1:
371                test_name += ' ' + str(test_index)
372            if test_name not in cfg['Soundtracks']:
373                break
374            test_index += 1
375
376        cfg['Soundtracks'][test_name] = copy.deepcopy(sdtk)
377        cfg.commit()
378        self._refresh(select_soundtrack=test_name)
379
380    def _select(self, name: str, index: int) -> None:
381        assert bui.app.classic is not None
382        music = bui.app.classic.music
383        self._selected_soundtrack_index = index
384        self._selected_soundtrack = name
385        cfg = bui.app.config
386        current_soundtrack = cfg.setdefault('Soundtrack', '__default__')
387
388        # If it varies from current, commit and play.
389        if current_soundtrack != name and self._allow_changing_soundtracks:
390            bui.getsound('gunCocking').play()
391            cfg['Soundtrack'] = self._selected_soundtrack
392            cfg.commit()
393
394            # Just play whats already playing.. this'll grab it from the
395            # new soundtrack.
396            music.do_play_music(
397                music.music_types[bui.app.classic.MusicPlayMode.REGULAR]
398            )
399
400    # def _back(self) -> None:
401    #     # pylint: disable=cyclic-import
402    #     from bauiv1lib.settings.audio import AudioSettingsWindow
403
404    #     # no-op if our underlying widget is dead or on its way out.
405    #     if not self._root_widget or self._root_widget.transitioning_out:
406    #         return
407
408    #     self._save_state()
409    #     bui.containerwidget(
410    #         edit=self._root_widget, transition=self._transition_out
411    #     )
412    #     assert bui.app.classic is not None
413    #     bui.app.ui_v1.set_main_window(
414    #         AudioSettingsWindow(transition='in_left'),
415    #         from_window=self,
416    #         is_back=True,
417    #     )
418
419    def _edit_soundtrack_with_sound(self) -> None:
420        # pylint: disable=cyclic-import
421        from bauiv1lib.purchase import PurchaseWindow
422
423        if REQUIRE_PRO and (
424            bui.app.classic is not None
425            and not bui.app.classic.accounts.have_pro_options()
426        ):
427            PurchaseWindow(items=['pro'])
428            return
429        bui.getsound('swish').play()
430        self._edit_soundtrack()
431
432    def _edit_soundtrack(self) -> None:
433        # pylint: disable=cyclic-import
434        from bauiv1lib.purchase import PurchaseWindow
435        from bauiv1lib.soundtrack.edit import SoundtrackEditWindow
436
437        if UNDER_CONSTRUCTION:
438            bui.screenmessage('UNDER CONSTRUCTION')
439            return
440
441        # no-op if our underlying widget is dead or on its way out.
442        if not self._root_widget or self._root_widget.transitioning_out:
443            return
444
445        if REQUIRE_PRO and (
446            bui.app.classic is not None
447            and not bui.app.classic.accounts.have_pro_options()
448        ):
449            PurchaseWindow(items=['pro'])
450            return
451        if self._selected_soundtrack is None:
452            return
453        if self._selected_soundtrack == '__default__':
454            bui.getsound('error').play()
455            bui.screenmessage(
456                bui.Lstr(resource=f'{self._r}.cantEditDefaultText'),
457                color=(1, 0, 0),
458            )
459            return
460
461        self._save_state()
462        bui.containerwidget(edit=self._root_widget, transition='out_left')
463        assert bui.app.classic is not None
464        bui.app.ui_v1.set_main_window(
465            SoundtrackEditWindow(existing_soundtrack=self._selected_soundtrack),
466            from_window=self,
467        )
468
469    def _get_soundtrack_display_name(self, soundtrack: str) -> bui.Lstr:
470        if soundtrack == '__default__':
471            return bui.Lstr(resource=f'{self._r}.defaultSoundtrackNameText')
472        return bui.Lstr(value=soundtrack)
473
474    def _refresh(self, select_soundtrack: str | None = None) -> None:
475        from efro.util import asserttype
476
477        self._allow_changing_soundtracks = False
478        old_selection = self._selected_soundtrack
479
480        # If there was no prev selection, look in prefs.
481        if old_selection is None:
482            old_selection = bui.app.config.get('Soundtrack')
483        old_selection_index = self._selected_soundtrack_index
484
485        # Delete old.
486        while self._soundtrack_widgets:
487            self._soundtrack_widgets.pop().delete()
488
489        self._soundtracks = bui.app.config.get('Soundtracks', {})
490        assert self._soundtracks is not None
491        items = list(self._soundtracks.items())
492        items.sort(key=lambda x: asserttype(x[0], str).lower())
493        items = [('__default__', None)] + items  # default is always first
494        index = 0
495        for pname, _pval in items:
496            assert pname is not None
497            txtw = bui.textwidget(
498                parent=self._col,
499                size=(self._width - 40, 24),
500                text=self._get_soundtrack_display_name(pname),
501                h_align='left',
502                v_align='center',
503                maxwidth=self._width - 110,
504                always_highlight=True,
505                on_select_call=bui.WeakCall(self._select, pname, index),
506                on_activate_call=self._edit_soundtrack_with_sound,
507                selectable=True,
508            )
509            if index == 0:
510                bui.widget(edit=txtw, up_widget=self._back_button)
511            self._soundtrack_widgets.append(txtw)
512
513            # Select this one if the user requested it
514            if select_soundtrack is not None:
515                if pname == select_soundtrack:
516                    bui.columnwidget(
517                        edit=self._col, selected_child=txtw, visible_child=txtw
518                    )
519            else:
520                # Select this one if it was previously selected.
521                # Go by index if there's one.
522                if old_selection_index is not None:
523                    if index == old_selection_index:
524                        bui.columnwidget(
525                            edit=self._col,
526                            selected_child=txtw,
527                            visible_child=txtw,
528                        )
529                else:  # Otherwise look by name.
530                    if pname == old_selection:
531                        bui.columnwidget(
532                            edit=self._col,
533                            selected_child=txtw,
534                            visible_child=txtw,
535                        )
536            index += 1
537
538        # Explicitly run select callback on current one and re-enable
539        # callbacks.
540
541        # Eww need to run this in a timer so it happens after our select
542        # callbacks. With a small-enough time sometimes it happens before
543        # anyway. Ew. need a way to just schedule a callable i guess.
544        bui.apptimer(0.1, bui.WeakCall(self._set_allow_changing))
545
546    def _set_allow_changing(self) -> None:
547        self._allow_changing_soundtracks = True
548        assert self._selected_soundtrack is not None
549        assert self._selected_soundtrack_index is not None
550        self._select(self._selected_soundtrack, self._selected_soundtrack_index)
551
552    def _new_soundtrack(self) -> None:
553        # pylint: disable=cyclic-import
554        from bauiv1lib.purchase import PurchaseWindow
555        from bauiv1lib.soundtrack.edit import SoundtrackEditWindow
556
557        if UNDER_CONSTRUCTION:
558            bui.screenmessage('UNDER CONSTRUCTION')
559            return
560
561        if REQUIRE_PRO and (
562            bui.app.classic is not None
563            and not bui.app.classic.accounts.have_pro_options()
564        ):
565            PurchaseWindow(items=['pro'])
566            return
567        self._save_state()
568        bui.containerwidget(edit=self._root_widget, transition='out_left')
569        bui.app.ui_v1.set_main_window(
570            SoundtrackEditWindow(existing_soundtrack=None), from_window=self
571        )
572
573    def _create_done(self, new_soundtrack: str) -> None:
574        if new_soundtrack is not None:
575            bui.getsound('gunCocking').play()
576            self._refresh(select_soundtrack=new_soundtrack)
577
578    def _save_state(self) -> None:
579        try:
580            sel = self._root_widget.get_selected_child()
581            if sel == self._scrollwidget:
582                sel_name = 'Scroll'
583            elif sel == self._new_button:
584                sel_name = 'New'
585            elif sel == self._edit_button:
586                sel_name = 'Edit'
587            elif sel == self._duplicate_button:
588                sel_name = 'Duplicate'
589            elif sel == self._delete_button:
590                sel_name = 'Delete'
591            elif sel == self._back_button:
592                sel_name = 'Back'
593            else:
594                raise ValueError(f'unrecognized selection \'{sel}\'')
595            assert bui.app.classic is not None
596            bui.app.ui_v1.window_states[type(self)] = sel_name
597        except Exception:
598            logging.exception('Error saving state for %s.', self)
599
600    def _restore_state(self) -> None:
601        try:
602            assert bui.app.classic is not None
603            sel_name = bui.app.ui_v1.window_states.get(type(self))
604            if sel_name == 'Scroll':
605                sel = self._scrollwidget
606            elif sel_name == 'New':
607                sel = self._new_button
608            elif sel_name == 'Edit':
609                sel = self._edit_button
610            elif sel_name == 'Duplicate':
611                sel = self._duplicate_button
612            elif sel_name == 'Delete':
613                sel = self._delete_button
614            else:
615                sel = self._scrollwidget
616            bui.containerwidget(edit=self._root_widget, selected_child=sel)
617        except Exception:
618            logging.exception('Error restoring state for %s.', self)
REQUIRE_PRO = False
UNDER_CONSTRUCTION = True
class SoundtrackBrowserWindow(bauiv1._uitypes.MainWindow):
 23class SoundtrackBrowserWindow(bui.MainWindow):
 24    """Window for browsing soundtracks."""
 25
 26    def __init__(
 27        self,
 28        transition: str | None = 'in_right',
 29        origin_widget: bui.Widget | None = None,
 30    ):
 31        # pylint: disable=too-many-statements
 32
 33        self._r = 'editSoundtrackWindow'
 34        assert bui.app.classic is not None
 35        uiscale = bui.app.ui_v1.uiscale
 36        self._width = 800 if uiscale is bui.UIScale.SMALL else 600
 37        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 38        self._height = (
 39            340
 40            if uiscale is bui.UIScale.SMALL
 41            else 370 if uiscale is bui.UIScale.MEDIUM else 440
 42        )
 43        spacing = 40.0
 44        v = self._height - 40.0
 45        v -= spacing * 1.0
 46
 47        super().__init__(
 48            root_widget=bui.containerwidget(
 49                size=(self._width, self._height),
 50                toolbar_visibility=(
 51                    'menu_minimal'
 52                    if uiscale is bui.UIScale.SMALL
 53                    else 'menu_full'
 54                ),
 55                scale=(
 56                    2.1
 57                    if uiscale is bui.UIScale.SMALL
 58                    else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0
 59                ),
 60                stack_offset=(
 61                    (0, -18) if uiscale is bui.UIScale.SMALL else (0, 0)
 62                ),
 63            ),
 64            transition=transition,
 65            origin_widget=origin_widget,
 66        )
 67
 68        assert bui.app.classic is not None
 69        if uiscale is bui.UIScale.SMALL:
 70            self._back_button = None
 71        else:
 72            self._back_button = bui.buttonwidget(
 73                parent=self._root_widget,
 74                position=(45 + x_inset, self._height - 60),
 75                size=(120, 60),
 76                scale=0.8,
 77                label=bui.Lstr(resource='backText'),
 78                button_type='back',
 79                autoselect=True,
 80            )
 81            bui.buttonwidget(
 82                edit=self._back_button,
 83                button_type='backSmall',
 84                size=(60, 60),
 85                label=bui.charstr(bui.SpecialChar.BACK),
 86            )
 87        bui.textwidget(
 88            parent=self._root_widget,
 89            position=(self._width * 0.5, self._height - 35),
 90            size=(0, 0),
 91            maxwidth=300,
 92            text=bui.Lstr(resource=f'{self._r}.titleText'),
 93            color=bui.app.ui_v1.title_color,
 94            h_align='center',
 95            v_align='center',
 96        )
 97
 98        h = 43 + x_inset
 99        v = self._height - 60
100        b_color = (0.6, 0.53, 0.63)
101        b_textcolor = (0.75, 0.7, 0.8)
102        lock_tex = bui.gettexture('lock')
103        self._lock_images: list[bui.Widget] = []
104
105        scl = (
106            1.0
107            if uiscale is bui.UIScale.SMALL
108            else 1.13 if uiscale is bui.UIScale.MEDIUM else 1.4
109        )
110        v -= 60.0 * scl
111        self._new_button = btn = bui.buttonwidget(
112            parent=self._root_widget,
113            position=(h, v),
114            size=(100, 55.0 * scl),
115            on_activate_call=self._new_soundtrack,
116            color=b_color,
117            button_type='square',
118            autoselect=True,
119            textcolor=b_textcolor,
120            text_scale=0.7,
121            label=bui.Lstr(resource=f'{self._r}.newText'),
122        )
123        self._lock_images.append(
124            bui.imagewidget(
125                parent=self._root_widget,
126                size=(30, 30),
127                draw_controller=btn,
128                position=(h - 10, v + 55.0 * scl - 28),
129                texture=lock_tex,
130            )
131        )
132
133        if self._back_button is None:
134            bui.widget(
135                edit=btn,
136                left_widget=bui.get_special_widget('back_button'),
137            )
138        v -= 60.0 * scl
139
140        self._edit_button = btn = bui.buttonwidget(
141            parent=self._root_widget,
142            position=(h, v),
143            size=(100, 55.0 * scl),
144            on_activate_call=self._edit_soundtrack,
145            color=b_color,
146            button_type='square',
147            autoselect=True,
148            textcolor=b_textcolor,
149            text_scale=0.7,
150            label=bui.Lstr(resource=f'{self._r}.editText'),
151        )
152        self._lock_images.append(
153            bui.imagewidget(
154                parent=self._root_widget,
155                size=(30, 30),
156                draw_controller=btn,
157                position=(h - 10, v + 55.0 * scl - 28),
158                texture=lock_tex,
159            )
160        )
161        if self._back_button is None:
162            bui.widget(
163                edit=btn,
164                left_widget=bui.get_special_widget('back_button'),
165            )
166        v -= 60.0 * scl
167
168        self._duplicate_button = btn = bui.buttonwidget(
169            parent=self._root_widget,
170            position=(h, v),
171            size=(100, 55.0 * scl),
172            on_activate_call=self._duplicate_soundtrack,
173            button_type='square',
174            autoselect=True,
175            color=b_color,
176            textcolor=b_textcolor,
177            text_scale=0.7,
178            label=bui.Lstr(resource=f'{self._r}.duplicateText'),
179        )
180        self._lock_images.append(
181            bui.imagewidget(
182                parent=self._root_widget,
183                size=(30, 30),
184                draw_controller=btn,
185                position=(h - 10, v + 55.0 * scl - 28),
186                texture=lock_tex,
187            )
188        )
189        if self._back_button is None:
190            bui.widget(
191                edit=btn,
192                left_widget=bui.get_special_widget('back_button'),
193            )
194        v -= 60.0 * scl
195
196        self._delete_button = btn = bui.buttonwidget(
197            parent=self._root_widget,
198            position=(h, v),
199            size=(100, 55.0 * scl),
200            on_activate_call=self._delete_soundtrack,
201            color=b_color,
202            button_type='square',
203            autoselect=True,
204            textcolor=b_textcolor,
205            text_scale=0.7,
206            label=bui.Lstr(resource=f'{self._r}.deleteText'),
207        )
208        self._lock_images.append(
209            bui.imagewidget(
210                parent=self._root_widget,
211                size=(30, 30),
212                draw_controller=btn,
213                position=(h - 10, v + 55.0 * scl - 28),
214                texture=lock_tex,
215            )
216        )
217        if self._back_button is None:
218            bui.widget(
219                edit=btn,
220                left_widget=bui.get_special_widget('back_button'),
221            )
222
223        # Keep our lock images up to date/etc.
224        self._update_timer = bui.AppTimer(
225            1.0, bui.WeakCall(self._update), repeat=True
226        )
227        self._update()
228
229        v = self._height - 65
230        scroll_height = self._height - 105
231        v -= scroll_height
232        self._scrollwidget = scrollwidget = bui.scrollwidget(
233            parent=self._root_widget,
234            position=(152 + x_inset, v),
235            highlight=False,
236            size=(self._width - (205 + 2 * x_inset), scroll_height),
237        )
238        bui.widget(
239            edit=self._scrollwidget,
240            left_widget=self._new_button,
241            right_widget=bui.get_special_widget('squad_button'),
242        )
243        self._col = bui.columnwidget(parent=scrollwidget, border=2, margin=0)
244
245        self._soundtracks: dict[str, Any] | None = None
246        self._selected_soundtrack: str | None = None
247        self._selected_soundtrack_index: int | None = None
248        self._soundtrack_widgets: list[bui.Widget] = []
249        self._allow_changing_soundtracks = False
250        self._refresh()
251        if self._back_button is not None:
252            bui.buttonwidget(
253                edit=self._back_button, on_activate_call=self.main_window_back
254            )
255            bui.containerwidget(
256                edit=self._root_widget, cancel_button=self._back_button
257            )
258        else:
259            bui.containerwidget(
260                edit=self._root_widget, on_cancel_call=self.main_window_back
261            )
262
263    @override
264    def get_main_window_state(self) -> bui.MainWindowState:
265        # Support recreating our window for back/refresh purposes.
266        cls = type(self)
267        return bui.BasicMainWindowState(
268            create_call=lambda transition, origin_widget: cls(
269                transition=transition, origin_widget=origin_widget
270            )
271        )
272
273    @override
274    def on_main_window_close(self) -> None:
275        self._save_state()
276
277    def _update(self) -> None:
278        have_pro = (
279            bui.app.classic is None
280            or bui.app.classic.accounts.have_pro_options()
281        )
282        for lock in self._lock_images:
283            bui.imagewidget(
284                edit=lock, opacity=0.0 if (have_pro or not REQUIRE_PRO) else 1.0
285            )
286
287    def _do_delete_soundtrack(self) -> None:
288        cfg = bui.app.config
289        soundtracks = cfg.setdefault('Soundtracks', {})
290        if self._selected_soundtrack in soundtracks:
291            del soundtracks[self._selected_soundtrack]
292        cfg.commit()
293        bui.getsound('shieldDown').play()
294        assert self._selected_soundtrack_index is not None
295        assert self._soundtracks is not None
296        self._selected_soundtrack_index = min(
297            self._selected_soundtrack_index, len(self._soundtracks)
298        )
299        self._refresh()
300
301    def _delete_soundtrack(self) -> None:
302        # pylint: disable=cyclic-import
303        from bauiv1lib.purchase import PurchaseWindow
304        from bauiv1lib.confirm import ConfirmWindow
305
306        if REQUIRE_PRO and (
307            bui.app.classic is not None
308            and not bui.app.classic.accounts.have_pro_options()
309        ):
310            PurchaseWindow(items=['pro'])
311            return
312        if self._selected_soundtrack is None:
313            return
314        if self._selected_soundtrack == '__default__':
315            bui.getsound('error').play()
316            bui.screenmessage(
317                bui.Lstr(resource=f'{self._r}.cantDeleteDefaultText'),
318                color=(1, 0, 0),
319            )
320        else:
321            ConfirmWindow(
322                bui.Lstr(
323                    resource=f'{self._r}.deleteConfirmText',
324                    subs=[('${NAME}', self._selected_soundtrack)],
325                ),
326                self._do_delete_soundtrack,
327                450,
328                150,
329            )
330
331    def _duplicate_soundtrack(self) -> None:
332        # pylint: disable=cyclic-import
333        from bauiv1lib.purchase import PurchaseWindow
334
335        if REQUIRE_PRO and (
336            bui.app.classic is not None
337            and not bui.app.classic.accounts.have_pro_options()
338        ):
339            PurchaseWindow(items=['pro'])
340            return
341        cfg = bui.app.config
342        cfg.setdefault('Soundtracks', {})
343
344        if self._selected_soundtrack is None:
345            return
346        sdtk: dict[str, Any]
347        if self._selected_soundtrack == '__default__':
348            sdtk = {}
349        else:
350            sdtk = cfg['Soundtracks'][self._selected_soundtrack]
351
352        # Find a valid dup name that doesn't exist.
353        test_index = 1
354        copy_text = bui.Lstr(resource='copyOfText').evaluate()
355        # Get just 'Copy' or whatnot.
356        copy_word = copy_text.replace('${NAME}', '').strip()
357        base_name = self._get_soundtrack_display_name(
358            self._selected_soundtrack
359        ).evaluate()
360        assert isinstance(base_name, str)
361
362        # If it looks like a copy, strip digits and spaces off the end.
363        if copy_word in base_name:
364            while base_name[-1].isdigit() or base_name[-1] == ' ':
365                base_name = base_name[:-1]
366        while True:
367            if copy_word in base_name:
368                test_name = base_name
369            else:
370                test_name = copy_text.replace('${NAME}', base_name)
371            if test_index > 1:
372                test_name += ' ' + str(test_index)
373            if test_name not in cfg['Soundtracks']:
374                break
375            test_index += 1
376
377        cfg['Soundtracks'][test_name] = copy.deepcopy(sdtk)
378        cfg.commit()
379        self._refresh(select_soundtrack=test_name)
380
381    def _select(self, name: str, index: int) -> None:
382        assert bui.app.classic is not None
383        music = bui.app.classic.music
384        self._selected_soundtrack_index = index
385        self._selected_soundtrack = name
386        cfg = bui.app.config
387        current_soundtrack = cfg.setdefault('Soundtrack', '__default__')
388
389        # If it varies from current, commit and play.
390        if current_soundtrack != name and self._allow_changing_soundtracks:
391            bui.getsound('gunCocking').play()
392            cfg['Soundtrack'] = self._selected_soundtrack
393            cfg.commit()
394
395            # Just play whats already playing.. this'll grab it from the
396            # new soundtrack.
397            music.do_play_music(
398                music.music_types[bui.app.classic.MusicPlayMode.REGULAR]
399            )
400
401    # def _back(self) -> None:
402    #     # pylint: disable=cyclic-import
403    #     from bauiv1lib.settings.audio import AudioSettingsWindow
404
405    #     # no-op if our underlying widget is dead or on its way out.
406    #     if not self._root_widget or self._root_widget.transitioning_out:
407    #         return
408
409    #     self._save_state()
410    #     bui.containerwidget(
411    #         edit=self._root_widget, transition=self._transition_out
412    #     )
413    #     assert bui.app.classic is not None
414    #     bui.app.ui_v1.set_main_window(
415    #         AudioSettingsWindow(transition='in_left'),
416    #         from_window=self,
417    #         is_back=True,
418    #     )
419
420    def _edit_soundtrack_with_sound(self) -> None:
421        # pylint: disable=cyclic-import
422        from bauiv1lib.purchase import PurchaseWindow
423
424        if REQUIRE_PRO and (
425            bui.app.classic is not None
426            and not bui.app.classic.accounts.have_pro_options()
427        ):
428            PurchaseWindow(items=['pro'])
429            return
430        bui.getsound('swish').play()
431        self._edit_soundtrack()
432
433    def _edit_soundtrack(self) -> None:
434        # pylint: disable=cyclic-import
435        from bauiv1lib.purchase import PurchaseWindow
436        from bauiv1lib.soundtrack.edit import SoundtrackEditWindow
437
438        if UNDER_CONSTRUCTION:
439            bui.screenmessage('UNDER CONSTRUCTION')
440            return
441
442        # no-op if our underlying widget is dead or on its way out.
443        if not self._root_widget or self._root_widget.transitioning_out:
444            return
445
446        if REQUIRE_PRO and (
447            bui.app.classic is not None
448            and not bui.app.classic.accounts.have_pro_options()
449        ):
450            PurchaseWindow(items=['pro'])
451            return
452        if self._selected_soundtrack is None:
453            return
454        if self._selected_soundtrack == '__default__':
455            bui.getsound('error').play()
456            bui.screenmessage(
457                bui.Lstr(resource=f'{self._r}.cantEditDefaultText'),
458                color=(1, 0, 0),
459            )
460            return
461
462        self._save_state()
463        bui.containerwidget(edit=self._root_widget, transition='out_left')
464        assert bui.app.classic is not None
465        bui.app.ui_v1.set_main_window(
466            SoundtrackEditWindow(existing_soundtrack=self._selected_soundtrack),
467            from_window=self,
468        )
469
470    def _get_soundtrack_display_name(self, soundtrack: str) -> bui.Lstr:
471        if soundtrack == '__default__':
472            return bui.Lstr(resource=f'{self._r}.defaultSoundtrackNameText')
473        return bui.Lstr(value=soundtrack)
474
475    def _refresh(self, select_soundtrack: str | None = None) -> None:
476        from efro.util import asserttype
477
478        self._allow_changing_soundtracks = False
479        old_selection = self._selected_soundtrack
480
481        # If there was no prev selection, look in prefs.
482        if old_selection is None:
483            old_selection = bui.app.config.get('Soundtrack')
484        old_selection_index = self._selected_soundtrack_index
485
486        # Delete old.
487        while self._soundtrack_widgets:
488            self._soundtrack_widgets.pop().delete()
489
490        self._soundtracks = bui.app.config.get('Soundtracks', {})
491        assert self._soundtracks is not None
492        items = list(self._soundtracks.items())
493        items.sort(key=lambda x: asserttype(x[0], str).lower())
494        items = [('__default__', None)] + items  # default is always first
495        index = 0
496        for pname, _pval in items:
497            assert pname is not None
498            txtw = bui.textwidget(
499                parent=self._col,
500                size=(self._width - 40, 24),
501                text=self._get_soundtrack_display_name(pname),
502                h_align='left',
503                v_align='center',
504                maxwidth=self._width - 110,
505                always_highlight=True,
506                on_select_call=bui.WeakCall(self._select, pname, index),
507                on_activate_call=self._edit_soundtrack_with_sound,
508                selectable=True,
509            )
510            if index == 0:
511                bui.widget(edit=txtw, up_widget=self._back_button)
512            self._soundtrack_widgets.append(txtw)
513
514            # Select this one if the user requested it
515            if select_soundtrack is not None:
516                if pname == select_soundtrack:
517                    bui.columnwidget(
518                        edit=self._col, selected_child=txtw, visible_child=txtw
519                    )
520            else:
521                # Select this one if it was previously selected.
522                # Go by index if there's one.
523                if old_selection_index is not None:
524                    if index == old_selection_index:
525                        bui.columnwidget(
526                            edit=self._col,
527                            selected_child=txtw,
528                            visible_child=txtw,
529                        )
530                else:  # Otherwise look by name.
531                    if pname == old_selection:
532                        bui.columnwidget(
533                            edit=self._col,
534                            selected_child=txtw,
535                            visible_child=txtw,
536                        )
537            index += 1
538
539        # Explicitly run select callback on current one and re-enable
540        # callbacks.
541
542        # Eww need to run this in a timer so it happens after our select
543        # callbacks. With a small-enough time sometimes it happens before
544        # anyway. Ew. need a way to just schedule a callable i guess.
545        bui.apptimer(0.1, bui.WeakCall(self._set_allow_changing))
546
547    def _set_allow_changing(self) -> None:
548        self._allow_changing_soundtracks = True
549        assert self._selected_soundtrack is not None
550        assert self._selected_soundtrack_index is not None
551        self._select(self._selected_soundtrack, self._selected_soundtrack_index)
552
553    def _new_soundtrack(self) -> None:
554        # pylint: disable=cyclic-import
555        from bauiv1lib.purchase import PurchaseWindow
556        from bauiv1lib.soundtrack.edit import SoundtrackEditWindow
557
558        if UNDER_CONSTRUCTION:
559            bui.screenmessage('UNDER CONSTRUCTION')
560            return
561
562        if REQUIRE_PRO and (
563            bui.app.classic is not None
564            and not bui.app.classic.accounts.have_pro_options()
565        ):
566            PurchaseWindow(items=['pro'])
567            return
568        self._save_state()
569        bui.containerwidget(edit=self._root_widget, transition='out_left')
570        bui.app.ui_v1.set_main_window(
571            SoundtrackEditWindow(existing_soundtrack=None), from_window=self
572        )
573
574    def _create_done(self, new_soundtrack: str) -> None:
575        if new_soundtrack is not None:
576            bui.getsound('gunCocking').play()
577            self._refresh(select_soundtrack=new_soundtrack)
578
579    def _save_state(self) -> None:
580        try:
581            sel = self._root_widget.get_selected_child()
582            if sel == self._scrollwidget:
583                sel_name = 'Scroll'
584            elif sel == self._new_button:
585                sel_name = 'New'
586            elif sel == self._edit_button:
587                sel_name = 'Edit'
588            elif sel == self._duplicate_button:
589                sel_name = 'Duplicate'
590            elif sel == self._delete_button:
591                sel_name = 'Delete'
592            elif sel == self._back_button:
593                sel_name = 'Back'
594            else:
595                raise ValueError(f'unrecognized selection \'{sel}\'')
596            assert bui.app.classic is not None
597            bui.app.ui_v1.window_states[type(self)] = sel_name
598        except Exception:
599            logging.exception('Error saving state for %s.', self)
600
601    def _restore_state(self) -> None:
602        try:
603            assert bui.app.classic is not None
604            sel_name = bui.app.ui_v1.window_states.get(type(self))
605            if sel_name == 'Scroll':
606                sel = self._scrollwidget
607            elif sel_name == 'New':
608                sel = self._new_button
609            elif sel_name == 'Edit':
610                sel = self._edit_button
611            elif sel_name == 'Duplicate':
612                sel = self._duplicate_button
613            elif sel_name == 'Delete':
614                sel = self._delete_button
615            else:
616                sel = self._scrollwidget
617            bui.containerwidget(edit=self._root_widget, selected_child=sel)
618        except Exception:
619            logging.exception('Error restoring state for %s.', self)

Window for browsing soundtracks.

SoundtrackBrowserWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
 26    def __init__(
 27        self,
 28        transition: str | None = 'in_right',
 29        origin_widget: bui.Widget | None = None,
 30    ):
 31        # pylint: disable=too-many-statements
 32
 33        self._r = 'editSoundtrackWindow'
 34        assert bui.app.classic is not None
 35        uiscale = bui.app.ui_v1.uiscale
 36        self._width = 800 if uiscale is bui.UIScale.SMALL else 600
 37        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 38        self._height = (
 39            340
 40            if uiscale is bui.UIScale.SMALL
 41            else 370 if uiscale is bui.UIScale.MEDIUM else 440
 42        )
 43        spacing = 40.0
 44        v = self._height - 40.0
 45        v -= spacing * 1.0
 46
 47        super().__init__(
 48            root_widget=bui.containerwidget(
 49                size=(self._width, self._height),
 50                toolbar_visibility=(
 51                    'menu_minimal'
 52                    if uiscale is bui.UIScale.SMALL
 53                    else 'menu_full'
 54                ),
 55                scale=(
 56                    2.1
 57                    if uiscale is bui.UIScale.SMALL
 58                    else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0
 59                ),
 60                stack_offset=(
 61                    (0, -18) if uiscale is bui.UIScale.SMALL else (0, 0)
 62                ),
 63            ),
 64            transition=transition,
 65            origin_widget=origin_widget,
 66        )
 67
 68        assert bui.app.classic is not None
 69        if uiscale is bui.UIScale.SMALL:
 70            self._back_button = None
 71        else:
 72            self._back_button = bui.buttonwidget(
 73                parent=self._root_widget,
 74                position=(45 + x_inset, self._height - 60),
 75                size=(120, 60),
 76                scale=0.8,
 77                label=bui.Lstr(resource='backText'),
 78                button_type='back',
 79                autoselect=True,
 80            )
 81            bui.buttonwidget(
 82                edit=self._back_button,
 83                button_type='backSmall',
 84                size=(60, 60),
 85                label=bui.charstr(bui.SpecialChar.BACK),
 86            )
 87        bui.textwidget(
 88            parent=self._root_widget,
 89            position=(self._width * 0.5, self._height - 35),
 90            size=(0, 0),
 91            maxwidth=300,
 92            text=bui.Lstr(resource=f'{self._r}.titleText'),
 93            color=bui.app.ui_v1.title_color,
 94            h_align='center',
 95            v_align='center',
 96        )
 97
 98        h = 43 + x_inset
 99        v = self._height - 60
100        b_color = (0.6, 0.53, 0.63)
101        b_textcolor = (0.75, 0.7, 0.8)
102        lock_tex = bui.gettexture('lock')
103        self._lock_images: list[bui.Widget] = []
104
105        scl = (
106            1.0
107            if uiscale is bui.UIScale.SMALL
108            else 1.13 if uiscale is bui.UIScale.MEDIUM else 1.4
109        )
110        v -= 60.0 * scl
111        self._new_button = btn = bui.buttonwidget(
112            parent=self._root_widget,
113            position=(h, v),
114            size=(100, 55.0 * scl),
115            on_activate_call=self._new_soundtrack,
116            color=b_color,
117            button_type='square',
118            autoselect=True,
119            textcolor=b_textcolor,
120            text_scale=0.7,
121            label=bui.Lstr(resource=f'{self._r}.newText'),
122        )
123        self._lock_images.append(
124            bui.imagewidget(
125                parent=self._root_widget,
126                size=(30, 30),
127                draw_controller=btn,
128                position=(h - 10, v + 55.0 * scl - 28),
129                texture=lock_tex,
130            )
131        )
132
133        if self._back_button is None:
134            bui.widget(
135                edit=btn,
136                left_widget=bui.get_special_widget('back_button'),
137            )
138        v -= 60.0 * scl
139
140        self._edit_button = btn = bui.buttonwidget(
141            parent=self._root_widget,
142            position=(h, v),
143            size=(100, 55.0 * scl),
144            on_activate_call=self._edit_soundtrack,
145            color=b_color,
146            button_type='square',
147            autoselect=True,
148            textcolor=b_textcolor,
149            text_scale=0.7,
150            label=bui.Lstr(resource=f'{self._r}.editText'),
151        )
152        self._lock_images.append(
153            bui.imagewidget(
154                parent=self._root_widget,
155                size=(30, 30),
156                draw_controller=btn,
157                position=(h - 10, v + 55.0 * scl - 28),
158                texture=lock_tex,
159            )
160        )
161        if self._back_button is None:
162            bui.widget(
163                edit=btn,
164                left_widget=bui.get_special_widget('back_button'),
165            )
166        v -= 60.0 * scl
167
168        self._duplicate_button = btn = bui.buttonwidget(
169            parent=self._root_widget,
170            position=(h, v),
171            size=(100, 55.0 * scl),
172            on_activate_call=self._duplicate_soundtrack,
173            button_type='square',
174            autoselect=True,
175            color=b_color,
176            textcolor=b_textcolor,
177            text_scale=0.7,
178            label=bui.Lstr(resource=f'{self._r}.duplicateText'),
179        )
180        self._lock_images.append(
181            bui.imagewidget(
182                parent=self._root_widget,
183                size=(30, 30),
184                draw_controller=btn,
185                position=(h - 10, v + 55.0 * scl - 28),
186                texture=lock_tex,
187            )
188        )
189        if self._back_button is None:
190            bui.widget(
191                edit=btn,
192                left_widget=bui.get_special_widget('back_button'),
193            )
194        v -= 60.0 * scl
195
196        self._delete_button = btn = bui.buttonwidget(
197            parent=self._root_widget,
198            position=(h, v),
199            size=(100, 55.0 * scl),
200            on_activate_call=self._delete_soundtrack,
201            color=b_color,
202            button_type='square',
203            autoselect=True,
204            textcolor=b_textcolor,
205            text_scale=0.7,
206            label=bui.Lstr(resource=f'{self._r}.deleteText'),
207        )
208        self._lock_images.append(
209            bui.imagewidget(
210                parent=self._root_widget,
211                size=(30, 30),
212                draw_controller=btn,
213                position=(h - 10, v + 55.0 * scl - 28),
214                texture=lock_tex,
215            )
216        )
217        if self._back_button is None:
218            bui.widget(
219                edit=btn,
220                left_widget=bui.get_special_widget('back_button'),
221            )
222
223        # Keep our lock images up to date/etc.
224        self._update_timer = bui.AppTimer(
225            1.0, bui.WeakCall(self._update), repeat=True
226        )
227        self._update()
228
229        v = self._height - 65
230        scroll_height = self._height - 105
231        v -= scroll_height
232        self._scrollwidget = scrollwidget = bui.scrollwidget(
233            parent=self._root_widget,
234            position=(152 + x_inset, v),
235            highlight=False,
236            size=(self._width - (205 + 2 * x_inset), scroll_height),
237        )
238        bui.widget(
239            edit=self._scrollwidget,
240            left_widget=self._new_button,
241            right_widget=bui.get_special_widget('squad_button'),
242        )
243        self._col = bui.columnwidget(parent=scrollwidget, border=2, margin=0)
244
245        self._soundtracks: dict[str, Any] | None = None
246        self._selected_soundtrack: str | None = None
247        self._selected_soundtrack_index: int | None = None
248        self._soundtrack_widgets: list[bui.Widget] = []
249        self._allow_changing_soundtracks = False
250        self._refresh()
251        if self._back_button is not None:
252            bui.buttonwidget(
253                edit=self._back_button, on_activate_call=self.main_window_back
254            )
255            bui.containerwidget(
256                edit=self._root_widget, cancel_button=self._back_button
257            )
258        else:
259            bui.containerwidget(
260                edit=self._root_widget, on_cancel_call=self.main_window_back
261            )

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:
263    @override
264    def get_main_window_state(self) -> bui.MainWindowState:
265        # Support recreating our window for back/refresh purposes.
266        cls = type(self)
267        return bui.BasicMainWindowState(
268            create_call=lambda transition, origin_widget: cls(
269                transition=transition, origin_widget=origin_widget
270            )
271        )

Return a WindowState to recreate this window, if supported.

@override
def on_main_window_close(self) -> None:
273    @override
274    def on_main_window_close(self) -> None:
275        self._save_state()

Called before transitioning out a main window.

A good opportunity to save window state/etc.

Inherited Members
bauiv1._uitypes.MainWindow
main_window_back_state
main_window_close
can_change_main_window
main_window_back
main_window_replace
bauiv1._uitypes.Window
get_root_widget