bauiv1lib.credits

Provides a window to display game credits.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides a window to display game credits."""
  4
  5from __future__ import annotations
  6
  7import os
  8import json
  9import logging
 10from typing import TYPE_CHECKING, override
 11
 12import bauiv1 as bui
 13
 14if TYPE_CHECKING:
 15    from typing import Sequence
 16
 17
 18class CreditsWindow(bui.MainWindow):
 19    """Window for displaying game credits."""
 20
 21    def __init__(
 22        self,
 23        transition: str | None = 'in_right',
 24        origin_widget: bui.Widget | None = None,
 25    ):
 26        # pylint: disable=too-many-locals
 27        # pylint: disable=too-many-statements
 28
 29        bui.set_analytics_screen('Credits Window')
 30
 31        assert bui.app.classic is not None
 32        uiscale = bui.app.ui_v1.uiscale
 33        width = 990 if uiscale is bui.UIScale.SMALL else 670
 34        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 35        height = 450 if uiscale is bui.UIScale.SMALL else 500
 36
 37        self._r = 'creditsWindow'
 38        super().__init__(
 39            root_widget=bui.containerwidget(
 40                size=(width, height),
 41                toolbar_visibility=(
 42                    'menu_minimal'
 43                    if uiscale is bui.UIScale.SMALL
 44                    else 'menu_full'
 45                ),
 46                scale=(
 47                    1.8
 48                    if uiscale is bui.UIScale.SMALL
 49                    else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0
 50                ),
 51                stack_offset=(
 52                    (0, 0) if uiscale is bui.UIScale.SMALL else (0, 0)
 53                ),
 54            ),
 55            transition=transition,
 56            origin_widget=origin_widget,
 57        )
 58
 59        if uiscale is bui.UIScale.SMALL:
 60            bui.containerwidget(
 61                edit=self._root_widget, on_cancel_call=self.main_window_back
 62            )
 63        else:
 64            btn = bui.buttonwidget(
 65                parent=self._root_widget,
 66                position=(40 + x_inset, height - 62),
 67                size=(140, 60),
 68                scale=0.8,
 69                label=bui.Lstr(resource='backText'),
 70                button_type='back',
 71                on_activate_call=self.main_window_back,
 72                autoselect=True,
 73            )
 74            bui.containerwidget(edit=self._root_widget, cancel_button=btn)
 75
 76            bui.buttonwidget(
 77                edit=btn,
 78                button_type='backSmall',
 79                position=(
 80                    40 + x_inset,
 81                    height - 62 + 5,
 82                ),
 83                size=(60, 48),
 84                label=bui.charstr(bui.SpecialChar.BACK),
 85            )
 86
 87        bui.textwidget(
 88            parent=self._root_widget,
 89            position=(0, height - (93 if uiscale is bui.UIScale.SMALL else 54)),
 90            size=(width, 30),
 91            scale=0.8 if uiscale is bui.UIScale.SMALL else 1.0,
 92            text=bui.Lstr(
 93                resource=f'{self._r}.titleText',
 94                subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))],
 95            ),
 96            h_align='center',
 97            color=bui.app.ui_v1.title_color,
 98            maxwidth=330,
 99            v_align='center',
100        )
101
102        scroll = bui.scrollwidget(
103            parent=self._root_widget,
104            position=(40 + x_inset, 62 if uiscale is bui.UIScale.SMALL else 35),
105            size=(
106                width - (80 + 2 * x_inset),
107                height - (160 if uiscale is bui.UIScale.SMALL else 100),
108            ),
109            capture_arrows=True,
110            border_opacity=0.4,
111        )
112
113        bui.widget(
114            edit=scroll,
115            right_widget=bui.get_special_widget('squad_button'),
116        )
117        if uiscale is bui.UIScale.SMALL:
118            bui.widget(
119                edit=scroll,
120                left_widget=bui.get_special_widget('back_button'),
121            )
122
123        def _format_names(names2: Sequence[str], inset: float) -> str:
124            sval = ''
125            # measure a series since there's overlaps and stuff..
126            space_width = (
127                bui.get_string_width(' ' * 10, suppress_warning=True) / 10.0
128            )
129            spacing = 330.0
130            col1 = inset
131            col2 = col1 + spacing
132            col3 = col2 + spacing
133            line_width = 0.0
134            nline = ''
135            for name in names2:
136                # move to the next column (or row) and print
137                if line_width > col3:
138                    sval += nline + '\n'
139                    nline = ''
140                    line_width = 0
141
142                if line_width > col2:
143                    target = col3
144                elif line_width > col1:
145                    target = col2
146                else:
147                    target = col1
148                spacingstr = ' ' * int((target - line_width) / space_width)
149                nline += spacingstr
150                nline += name
151                line_width = bui.get_string_width(nline, suppress_warning=True)
152            if nline != '':
153                sval += nline + '\n'
154            return sval
155
156        sound_and_music = bui.Lstr(
157            resource=f'{self._r}.songCreditText'
158        ).evaluate()
159        sound_and_music = sound_and_music.replace(
160            '${TITLE}', "'William Tell (Trumpet Entry)'"
161        )
162        sound_and_music = sound_and_music.replace(
163            '${PERFORMER}', 'The Apollo Symphony Orchestra'
164        )
165        sound_and_music = sound_and_music.replace(
166            '${PERFORMER}', 'The Apollo Symphony Orchestra'
167        )
168        sound_and_music = sound_and_music.replace(
169            '${COMPOSER}', 'Gioacchino Rossini'
170        )
171        sound_and_music = sound_and_music.replace('${ARRANGER}', 'Chris Worth')
172        sound_and_music = sound_and_music.replace('${PUBLISHER}', 'BMI')
173        sound_and_music = sound_and_music.replace(
174            '${SOURCE}', 'www.AudioSparx.com'
175        )
176        spc = '     '
177        sound_and_music = spc + sound_and_music.replace('\n', '\n' + spc)
178        names = [
179            'HubOfTheUniverseProd',
180            'Jovica',
181            'LG',
182            'Leady',
183            'Percy Duke',
184            'PhreaKsAccount',
185            'Pogotron',
186            'Rock Savage',
187            'anamorphosis',
188            'benboncan',
189            'cdrk',
190            'chipfork',
191            'guitarguy1985',
192            'jascha',
193            'joedeshon',
194            'loofa',
195            'm_O_m',
196            'mich3d',
197            'sandyrb',
198            'shakaharu',
199            'sirplus',
200            'stickman',
201            'thanvannispen',
202            'virotic',
203            'zimbot',
204        ]
205        names.sort(key=lambda x: x.lower())
206        freesound_names = _format_names(names, 90)
207
208        try:
209            with open(
210                os.path.join(
211                    bui.app.env.data_directory,
212                    'ba_data',
213                    'data',
214                    'langdata.json',
215                ),
216                encoding='utf-8',
217            ) as infile:
218                translation_contributors = json.loads(infile.read())[
219                    'translation_contributors'
220                ]
221        except Exception:
222            logging.exception('Error reading translation contributors.')
223            translation_contributors = []
224
225        translation_names = _format_names(translation_contributors, 60)
226
227        # Need to bake this out and chop it up since we're passing our
228        # 65535 vertex limit for meshes..
229        # We can remove that limit once we drop support for GL ES2.. :-/
230        # (or add mesh splitting under the hood)
231        credits_text = (
232            '  '
233            + bui.Lstr(resource=f'{self._r}.codingGraphicsAudioText')
234            .evaluate()
235            .replace('${NAME}', 'Eric Froemling')
236            + '\n'
237            '\n'
238            '  '
239            + bui.Lstr(resource=f'{self._r}.additionalAudioArtIdeasText')
240            .evaluate()
241            .replace('${NAME}', 'Raphael Suter')
242            + '\n'
243            '\n'
244            '  '
245            + bui.Lstr(resource=f'{self._r}.soundAndMusicText').evaluate()
246            + '\n'
247            '\n' + sound_and_music + '\n'
248            '\n'
249            '     '
250            + bui.Lstr(resource=f'{self._r}.publicDomainMusicViaText')
251            .evaluate()
252            .replace('${NAME}', 'Musopen.com')
253            + '\n'
254            '        '
255            + bui.Lstr(resource=f'{self._r}.thanksEspeciallyToText')
256            .evaluate()
257            .replace('${NAME}', 'the US Army, Navy, and Marine Bands')
258            + '\n'
259            '\n'
260            '     '
261            + bui.Lstr(resource=f'{self._r}.additionalMusicFromText')
262            .evaluate()
263            .replace('${NAME}', 'The YouTube Audio Library')
264            + '\n'
265            '\n'
266            '     '
267            + bui.Lstr(resource=f'{self._r}.soundsText')
268            .evaluate()
269            .replace('${SOURCE}', 'Freesound.org')
270            + '\n'
271            '\n' + freesound_names + '\n'
272            '\n'
273            '  '
274            + bui.Lstr(
275                resource=f'{self._r}.languageTranslationsText'
276            ).evaluate()
277            + '\n'
278            '\n'
279            + '\n'.join(translation_names.splitlines()[:146])
280            + '\n'.join(translation_names.splitlines()[146:])
281            + '\n'
282            '\n'
283            '  Shout Out to Awesome Mods / Modders / Contributors:\n\n'
284            '     BombDash ModPack\n'
285            '     TheMikirog & SoK - BombSquad Joyride Modpack\n'
286            '     Mrmaxmeier - BombSquad-Community-Mod-Manager\n'
287            '     Ritiek Malhotra \n'
288            '     Dliwk\n'
289            '     vishal332008\n'
290            '     itsre3\n'
291            '     Drooopyyy\n'
292            '\n'
293            '  Holiday theme vector art designed by Freepik\n'
294            '\n'
295            '  '
296            + bui.Lstr(resource=f'{self._r}.specialThanksText').evaluate()
297            + '\n'
298            '\n'
299            '     Todd, Laura, and Robert Froemling\n'
300            '     '
301            + bui.Lstr(resource=f'{self._r}.allMyFamilyText')
302            .evaluate()
303            .replace('\n', '\n     ')
304            + '\n'
305            '     '
306            + bui.Lstr(
307                resource=f'{self._r}.whoeverInventedCoffeeText'
308            ).evaluate()
309            + '\n'
310            '\n'
311            '  ' + bui.Lstr(resource=f'{self._r}.legalText').evaluate() + '\n'
312            '\n'
313            '     '
314            + bui.Lstr(resource=f'{self._r}.softwareBasedOnText')
315            .evaluate()
316            .replace('${NAME}', 'the Khronos Group')
317            + '\n'
318            '\n'
319            '                                       '
320            '                      www.ballistica.net\n'
321        )
322
323        txt = credits_text
324        lines = txt.splitlines()
325        line_height = 20
326
327        scale = 0.55
328        self._sub_width = width - 80
329        self._sub_height = line_height * len(lines) + 40
330
331        container = self._subcontainer = bui.containerwidget(
332            parent=scroll,
333            size=(self._sub_width, self._sub_height),
334            background=False,
335            claims_left_right=False,
336        )
337
338        voffs = 0
339        for line in lines:
340            bui.textwidget(
341                parent=container,
342                padding=4,
343                color=(0.7, 0.9, 0.7, 1.0),
344                scale=scale,
345                flatness=1.0,
346                size=(0, 0),
347                position=(0, self._sub_height - 20 + voffs),
348                h_align='left',
349                v_align='top',
350                text=bui.Lstr(value=line),
351            )
352            voffs -= line_height
353
354    @override
355    def get_main_window_state(self) -> bui.MainWindowState:
356        # Support recreating our window for back/refresh purposes.
357        cls = type(self)
358        return bui.BasicMainWindowState(
359            create_call=lambda transition, origin_widget: cls(
360                transition=transition, origin_widget=origin_widget
361            )
362        )
class CreditsWindow(bauiv1._uitypes.MainWindow):
 19class CreditsWindow(bui.MainWindow):
 20    """Window for displaying game credits."""
 21
 22    def __init__(
 23        self,
 24        transition: str | None = 'in_right',
 25        origin_widget: bui.Widget | None = None,
 26    ):
 27        # pylint: disable=too-many-locals
 28        # pylint: disable=too-many-statements
 29
 30        bui.set_analytics_screen('Credits Window')
 31
 32        assert bui.app.classic is not None
 33        uiscale = bui.app.ui_v1.uiscale
 34        width = 990 if uiscale is bui.UIScale.SMALL else 670
 35        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 36        height = 450 if uiscale is bui.UIScale.SMALL else 500
 37
 38        self._r = 'creditsWindow'
 39        super().__init__(
 40            root_widget=bui.containerwidget(
 41                size=(width, height),
 42                toolbar_visibility=(
 43                    'menu_minimal'
 44                    if uiscale is bui.UIScale.SMALL
 45                    else 'menu_full'
 46                ),
 47                scale=(
 48                    1.8
 49                    if uiscale is bui.UIScale.SMALL
 50                    else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0
 51                ),
 52                stack_offset=(
 53                    (0, 0) if uiscale is bui.UIScale.SMALL else (0, 0)
 54                ),
 55            ),
 56            transition=transition,
 57            origin_widget=origin_widget,
 58        )
 59
 60        if uiscale is bui.UIScale.SMALL:
 61            bui.containerwidget(
 62                edit=self._root_widget, on_cancel_call=self.main_window_back
 63            )
 64        else:
 65            btn = bui.buttonwidget(
 66                parent=self._root_widget,
 67                position=(40 + x_inset, height - 62),
 68                size=(140, 60),
 69                scale=0.8,
 70                label=bui.Lstr(resource='backText'),
 71                button_type='back',
 72                on_activate_call=self.main_window_back,
 73                autoselect=True,
 74            )
 75            bui.containerwidget(edit=self._root_widget, cancel_button=btn)
 76
 77            bui.buttonwidget(
 78                edit=btn,
 79                button_type='backSmall',
 80                position=(
 81                    40 + x_inset,
 82                    height - 62 + 5,
 83                ),
 84                size=(60, 48),
 85                label=bui.charstr(bui.SpecialChar.BACK),
 86            )
 87
 88        bui.textwidget(
 89            parent=self._root_widget,
 90            position=(0, height - (93 if uiscale is bui.UIScale.SMALL else 54)),
 91            size=(width, 30),
 92            scale=0.8 if uiscale is bui.UIScale.SMALL else 1.0,
 93            text=bui.Lstr(
 94                resource=f'{self._r}.titleText',
 95                subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))],
 96            ),
 97            h_align='center',
 98            color=bui.app.ui_v1.title_color,
 99            maxwidth=330,
100            v_align='center',
101        )
102
103        scroll = bui.scrollwidget(
104            parent=self._root_widget,
105            position=(40 + x_inset, 62 if uiscale is bui.UIScale.SMALL else 35),
106            size=(
107                width - (80 + 2 * x_inset),
108                height - (160 if uiscale is bui.UIScale.SMALL else 100),
109            ),
110            capture_arrows=True,
111            border_opacity=0.4,
112        )
113
114        bui.widget(
115            edit=scroll,
116            right_widget=bui.get_special_widget('squad_button'),
117        )
118        if uiscale is bui.UIScale.SMALL:
119            bui.widget(
120                edit=scroll,
121                left_widget=bui.get_special_widget('back_button'),
122            )
123
124        def _format_names(names2: Sequence[str], inset: float) -> str:
125            sval = ''
126            # measure a series since there's overlaps and stuff..
127            space_width = (
128                bui.get_string_width(' ' * 10, suppress_warning=True) / 10.0
129            )
130            spacing = 330.0
131            col1 = inset
132            col2 = col1 + spacing
133            col3 = col2 + spacing
134            line_width = 0.0
135            nline = ''
136            for name in names2:
137                # move to the next column (or row) and print
138                if line_width > col3:
139                    sval += nline + '\n'
140                    nline = ''
141                    line_width = 0
142
143                if line_width > col2:
144                    target = col3
145                elif line_width > col1:
146                    target = col2
147                else:
148                    target = col1
149                spacingstr = ' ' * int((target - line_width) / space_width)
150                nline += spacingstr
151                nline += name
152                line_width = bui.get_string_width(nline, suppress_warning=True)
153            if nline != '':
154                sval += nline + '\n'
155            return sval
156
157        sound_and_music = bui.Lstr(
158            resource=f'{self._r}.songCreditText'
159        ).evaluate()
160        sound_and_music = sound_and_music.replace(
161            '${TITLE}', "'William Tell (Trumpet Entry)'"
162        )
163        sound_and_music = sound_and_music.replace(
164            '${PERFORMER}', 'The Apollo Symphony Orchestra'
165        )
166        sound_and_music = sound_and_music.replace(
167            '${PERFORMER}', 'The Apollo Symphony Orchestra'
168        )
169        sound_and_music = sound_and_music.replace(
170            '${COMPOSER}', 'Gioacchino Rossini'
171        )
172        sound_and_music = sound_and_music.replace('${ARRANGER}', 'Chris Worth')
173        sound_and_music = sound_and_music.replace('${PUBLISHER}', 'BMI')
174        sound_and_music = sound_and_music.replace(
175            '${SOURCE}', 'www.AudioSparx.com'
176        )
177        spc = '     '
178        sound_and_music = spc + sound_and_music.replace('\n', '\n' + spc)
179        names = [
180            'HubOfTheUniverseProd',
181            'Jovica',
182            'LG',
183            'Leady',
184            'Percy Duke',
185            'PhreaKsAccount',
186            'Pogotron',
187            'Rock Savage',
188            'anamorphosis',
189            'benboncan',
190            'cdrk',
191            'chipfork',
192            'guitarguy1985',
193            'jascha',
194            'joedeshon',
195            'loofa',
196            'm_O_m',
197            'mich3d',
198            'sandyrb',
199            'shakaharu',
200            'sirplus',
201            'stickman',
202            'thanvannispen',
203            'virotic',
204            'zimbot',
205        ]
206        names.sort(key=lambda x: x.lower())
207        freesound_names = _format_names(names, 90)
208
209        try:
210            with open(
211                os.path.join(
212                    bui.app.env.data_directory,
213                    'ba_data',
214                    'data',
215                    'langdata.json',
216                ),
217                encoding='utf-8',
218            ) as infile:
219                translation_contributors = json.loads(infile.read())[
220                    'translation_contributors'
221                ]
222        except Exception:
223            logging.exception('Error reading translation contributors.')
224            translation_contributors = []
225
226        translation_names = _format_names(translation_contributors, 60)
227
228        # Need to bake this out and chop it up since we're passing our
229        # 65535 vertex limit for meshes..
230        # We can remove that limit once we drop support for GL ES2.. :-/
231        # (or add mesh splitting under the hood)
232        credits_text = (
233            '  '
234            + bui.Lstr(resource=f'{self._r}.codingGraphicsAudioText')
235            .evaluate()
236            .replace('${NAME}', 'Eric Froemling')
237            + '\n'
238            '\n'
239            '  '
240            + bui.Lstr(resource=f'{self._r}.additionalAudioArtIdeasText')
241            .evaluate()
242            .replace('${NAME}', 'Raphael Suter')
243            + '\n'
244            '\n'
245            '  '
246            + bui.Lstr(resource=f'{self._r}.soundAndMusicText').evaluate()
247            + '\n'
248            '\n' + sound_and_music + '\n'
249            '\n'
250            '     '
251            + bui.Lstr(resource=f'{self._r}.publicDomainMusicViaText')
252            .evaluate()
253            .replace('${NAME}', 'Musopen.com')
254            + '\n'
255            '        '
256            + bui.Lstr(resource=f'{self._r}.thanksEspeciallyToText')
257            .evaluate()
258            .replace('${NAME}', 'the US Army, Navy, and Marine Bands')
259            + '\n'
260            '\n'
261            '     '
262            + bui.Lstr(resource=f'{self._r}.additionalMusicFromText')
263            .evaluate()
264            .replace('${NAME}', 'The YouTube Audio Library')
265            + '\n'
266            '\n'
267            '     '
268            + bui.Lstr(resource=f'{self._r}.soundsText')
269            .evaluate()
270            .replace('${SOURCE}', 'Freesound.org')
271            + '\n'
272            '\n' + freesound_names + '\n'
273            '\n'
274            '  '
275            + bui.Lstr(
276                resource=f'{self._r}.languageTranslationsText'
277            ).evaluate()
278            + '\n'
279            '\n'
280            + '\n'.join(translation_names.splitlines()[:146])
281            + '\n'.join(translation_names.splitlines()[146:])
282            + '\n'
283            '\n'
284            '  Shout Out to Awesome Mods / Modders / Contributors:\n\n'
285            '     BombDash ModPack\n'
286            '     TheMikirog & SoK - BombSquad Joyride Modpack\n'
287            '     Mrmaxmeier - BombSquad-Community-Mod-Manager\n'
288            '     Ritiek Malhotra \n'
289            '     Dliwk\n'
290            '     vishal332008\n'
291            '     itsre3\n'
292            '     Drooopyyy\n'
293            '\n'
294            '  Holiday theme vector art designed by Freepik\n'
295            '\n'
296            '  '
297            + bui.Lstr(resource=f'{self._r}.specialThanksText').evaluate()
298            + '\n'
299            '\n'
300            '     Todd, Laura, and Robert Froemling\n'
301            '     '
302            + bui.Lstr(resource=f'{self._r}.allMyFamilyText')
303            .evaluate()
304            .replace('\n', '\n     ')
305            + '\n'
306            '     '
307            + bui.Lstr(
308                resource=f'{self._r}.whoeverInventedCoffeeText'
309            ).evaluate()
310            + '\n'
311            '\n'
312            '  ' + bui.Lstr(resource=f'{self._r}.legalText').evaluate() + '\n'
313            '\n'
314            '     '
315            + bui.Lstr(resource=f'{self._r}.softwareBasedOnText')
316            .evaluate()
317            .replace('${NAME}', 'the Khronos Group')
318            + '\n'
319            '\n'
320            '                                       '
321            '                      www.ballistica.net\n'
322        )
323
324        txt = credits_text
325        lines = txt.splitlines()
326        line_height = 20
327
328        scale = 0.55
329        self._sub_width = width - 80
330        self._sub_height = line_height * len(lines) + 40
331
332        container = self._subcontainer = bui.containerwidget(
333            parent=scroll,
334            size=(self._sub_width, self._sub_height),
335            background=False,
336            claims_left_right=False,
337        )
338
339        voffs = 0
340        for line in lines:
341            bui.textwidget(
342                parent=container,
343                padding=4,
344                color=(0.7, 0.9, 0.7, 1.0),
345                scale=scale,
346                flatness=1.0,
347                size=(0, 0),
348                position=(0, self._sub_height - 20 + voffs),
349                h_align='left',
350                v_align='top',
351                text=bui.Lstr(value=line),
352            )
353            voffs -= line_height
354
355    @override
356    def get_main_window_state(self) -> bui.MainWindowState:
357        # Support recreating our window for back/refresh purposes.
358        cls = type(self)
359        return bui.BasicMainWindowState(
360            create_call=lambda transition, origin_widget: cls(
361                transition=transition, origin_widget=origin_widget
362            )
363        )

Window for displaying game credits.

CreditsWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
 22    def __init__(
 23        self,
 24        transition: str | None = 'in_right',
 25        origin_widget: bui.Widget | None = None,
 26    ):
 27        # pylint: disable=too-many-locals
 28        # pylint: disable=too-many-statements
 29
 30        bui.set_analytics_screen('Credits Window')
 31
 32        assert bui.app.classic is not None
 33        uiscale = bui.app.ui_v1.uiscale
 34        width = 990 if uiscale is bui.UIScale.SMALL else 670
 35        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 36        height = 450 if uiscale is bui.UIScale.SMALL else 500
 37
 38        self._r = 'creditsWindow'
 39        super().__init__(
 40            root_widget=bui.containerwidget(
 41                size=(width, height),
 42                toolbar_visibility=(
 43                    'menu_minimal'
 44                    if uiscale is bui.UIScale.SMALL
 45                    else 'menu_full'
 46                ),
 47                scale=(
 48                    1.8
 49                    if uiscale is bui.UIScale.SMALL
 50                    else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0
 51                ),
 52                stack_offset=(
 53                    (0, 0) if uiscale is bui.UIScale.SMALL else (0, 0)
 54                ),
 55            ),
 56            transition=transition,
 57            origin_widget=origin_widget,
 58        )
 59
 60        if uiscale is bui.UIScale.SMALL:
 61            bui.containerwidget(
 62                edit=self._root_widget, on_cancel_call=self.main_window_back
 63            )
 64        else:
 65            btn = bui.buttonwidget(
 66                parent=self._root_widget,
 67                position=(40 + x_inset, height - 62),
 68                size=(140, 60),
 69                scale=0.8,
 70                label=bui.Lstr(resource='backText'),
 71                button_type='back',
 72                on_activate_call=self.main_window_back,
 73                autoselect=True,
 74            )
 75            bui.containerwidget(edit=self._root_widget, cancel_button=btn)
 76
 77            bui.buttonwidget(
 78                edit=btn,
 79                button_type='backSmall',
 80                position=(
 81                    40 + x_inset,
 82                    height - 62 + 5,
 83                ),
 84                size=(60, 48),
 85                label=bui.charstr(bui.SpecialChar.BACK),
 86            )
 87
 88        bui.textwidget(
 89            parent=self._root_widget,
 90            position=(0, height - (93 if uiscale is bui.UIScale.SMALL else 54)),
 91            size=(width, 30),
 92            scale=0.8 if uiscale is bui.UIScale.SMALL else 1.0,
 93            text=bui.Lstr(
 94                resource=f'{self._r}.titleText',
 95                subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))],
 96            ),
 97            h_align='center',
 98            color=bui.app.ui_v1.title_color,
 99            maxwidth=330,
100            v_align='center',
101        )
102
103        scroll = bui.scrollwidget(
104            parent=self._root_widget,
105            position=(40 + x_inset, 62 if uiscale is bui.UIScale.SMALL else 35),
106            size=(
107                width - (80 + 2 * x_inset),
108                height - (160 if uiscale is bui.UIScale.SMALL else 100),
109            ),
110            capture_arrows=True,
111            border_opacity=0.4,
112        )
113
114        bui.widget(
115            edit=scroll,
116            right_widget=bui.get_special_widget('squad_button'),
117        )
118        if uiscale is bui.UIScale.SMALL:
119            bui.widget(
120                edit=scroll,
121                left_widget=bui.get_special_widget('back_button'),
122            )
123
124        def _format_names(names2: Sequence[str], inset: float) -> str:
125            sval = ''
126            # measure a series since there's overlaps and stuff..
127            space_width = (
128                bui.get_string_width(' ' * 10, suppress_warning=True) / 10.0
129            )
130            spacing = 330.0
131            col1 = inset
132            col2 = col1 + spacing
133            col3 = col2 + spacing
134            line_width = 0.0
135            nline = ''
136            for name in names2:
137                # move to the next column (or row) and print
138                if line_width > col3:
139                    sval += nline + '\n'
140                    nline = ''
141                    line_width = 0
142
143                if line_width > col2:
144                    target = col3
145                elif line_width > col1:
146                    target = col2
147                else:
148                    target = col1
149                spacingstr = ' ' * int((target - line_width) / space_width)
150                nline += spacingstr
151                nline += name
152                line_width = bui.get_string_width(nline, suppress_warning=True)
153            if nline != '':
154                sval += nline + '\n'
155            return sval
156
157        sound_and_music = bui.Lstr(
158            resource=f'{self._r}.songCreditText'
159        ).evaluate()
160        sound_and_music = sound_and_music.replace(
161            '${TITLE}', "'William Tell (Trumpet Entry)'"
162        )
163        sound_and_music = sound_and_music.replace(
164            '${PERFORMER}', 'The Apollo Symphony Orchestra'
165        )
166        sound_and_music = sound_and_music.replace(
167            '${PERFORMER}', 'The Apollo Symphony Orchestra'
168        )
169        sound_and_music = sound_and_music.replace(
170            '${COMPOSER}', 'Gioacchino Rossini'
171        )
172        sound_and_music = sound_and_music.replace('${ARRANGER}', 'Chris Worth')
173        sound_and_music = sound_and_music.replace('${PUBLISHER}', 'BMI')
174        sound_and_music = sound_and_music.replace(
175            '${SOURCE}', 'www.AudioSparx.com'
176        )
177        spc = '     '
178        sound_and_music = spc + sound_and_music.replace('\n', '\n' + spc)
179        names = [
180            'HubOfTheUniverseProd',
181            'Jovica',
182            'LG',
183            'Leady',
184            'Percy Duke',
185            'PhreaKsAccount',
186            'Pogotron',
187            'Rock Savage',
188            'anamorphosis',
189            'benboncan',
190            'cdrk',
191            'chipfork',
192            'guitarguy1985',
193            'jascha',
194            'joedeshon',
195            'loofa',
196            'm_O_m',
197            'mich3d',
198            'sandyrb',
199            'shakaharu',
200            'sirplus',
201            'stickman',
202            'thanvannispen',
203            'virotic',
204            'zimbot',
205        ]
206        names.sort(key=lambda x: x.lower())
207        freesound_names = _format_names(names, 90)
208
209        try:
210            with open(
211                os.path.join(
212                    bui.app.env.data_directory,
213                    'ba_data',
214                    'data',
215                    'langdata.json',
216                ),
217                encoding='utf-8',
218            ) as infile:
219                translation_contributors = json.loads(infile.read())[
220                    'translation_contributors'
221                ]
222        except Exception:
223            logging.exception('Error reading translation contributors.')
224            translation_contributors = []
225
226        translation_names = _format_names(translation_contributors, 60)
227
228        # Need to bake this out and chop it up since we're passing our
229        # 65535 vertex limit for meshes..
230        # We can remove that limit once we drop support for GL ES2.. :-/
231        # (or add mesh splitting under the hood)
232        credits_text = (
233            '  '
234            + bui.Lstr(resource=f'{self._r}.codingGraphicsAudioText')
235            .evaluate()
236            .replace('${NAME}', 'Eric Froemling')
237            + '\n'
238            '\n'
239            '  '
240            + bui.Lstr(resource=f'{self._r}.additionalAudioArtIdeasText')
241            .evaluate()
242            .replace('${NAME}', 'Raphael Suter')
243            + '\n'
244            '\n'
245            '  '
246            + bui.Lstr(resource=f'{self._r}.soundAndMusicText').evaluate()
247            + '\n'
248            '\n' + sound_and_music + '\n'
249            '\n'
250            '     '
251            + bui.Lstr(resource=f'{self._r}.publicDomainMusicViaText')
252            .evaluate()
253            .replace('${NAME}', 'Musopen.com')
254            + '\n'
255            '        '
256            + bui.Lstr(resource=f'{self._r}.thanksEspeciallyToText')
257            .evaluate()
258            .replace('${NAME}', 'the US Army, Navy, and Marine Bands')
259            + '\n'
260            '\n'
261            '     '
262            + bui.Lstr(resource=f'{self._r}.additionalMusicFromText')
263            .evaluate()
264            .replace('${NAME}', 'The YouTube Audio Library')
265            + '\n'
266            '\n'
267            '     '
268            + bui.Lstr(resource=f'{self._r}.soundsText')
269            .evaluate()
270            .replace('${SOURCE}', 'Freesound.org')
271            + '\n'
272            '\n' + freesound_names + '\n'
273            '\n'
274            '  '
275            + bui.Lstr(
276                resource=f'{self._r}.languageTranslationsText'
277            ).evaluate()
278            + '\n'
279            '\n'
280            + '\n'.join(translation_names.splitlines()[:146])
281            + '\n'.join(translation_names.splitlines()[146:])
282            + '\n'
283            '\n'
284            '  Shout Out to Awesome Mods / Modders / Contributors:\n\n'
285            '     BombDash ModPack\n'
286            '     TheMikirog & SoK - BombSquad Joyride Modpack\n'
287            '     Mrmaxmeier - BombSquad-Community-Mod-Manager\n'
288            '     Ritiek Malhotra \n'
289            '     Dliwk\n'
290            '     vishal332008\n'
291            '     itsre3\n'
292            '     Drooopyyy\n'
293            '\n'
294            '  Holiday theme vector art designed by Freepik\n'
295            '\n'
296            '  '
297            + bui.Lstr(resource=f'{self._r}.specialThanksText').evaluate()
298            + '\n'
299            '\n'
300            '     Todd, Laura, and Robert Froemling\n'
301            '     '
302            + bui.Lstr(resource=f'{self._r}.allMyFamilyText')
303            .evaluate()
304            .replace('\n', '\n     ')
305            + '\n'
306            '     '
307            + bui.Lstr(
308                resource=f'{self._r}.whoeverInventedCoffeeText'
309            ).evaluate()
310            + '\n'
311            '\n'
312            '  ' + bui.Lstr(resource=f'{self._r}.legalText').evaluate() + '\n'
313            '\n'
314            '     '
315            + bui.Lstr(resource=f'{self._r}.softwareBasedOnText')
316            .evaluate()
317            .replace('${NAME}', 'the Khronos Group')
318            + '\n'
319            '\n'
320            '                                       '
321            '                      www.ballistica.net\n'
322        )
323
324        txt = credits_text
325        lines = txt.splitlines()
326        line_height = 20
327
328        scale = 0.55
329        self._sub_width = width - 80
330        self._sub_height = line_height * len(lines) + 40
331
332        container = self._subcontainer = bui.containerwidget(
333            parent=scroll,
334            size=(self._sub_width, self._sub_height),
335            background=False,
336            claims_left_right=False,
337        )
338
339        voffs = 0
340        for line in lines:
341            bui.textwidget(
342                parent=container,
343                padding=4,
344                color=(0.7, 0.9, 0.7, 1.0),
345                scale=scale,
346                flatness=1.0,
347                size=(0, 0),
348                position=(0, self._sub_height - 20 + voffs),
349                h_align='left',
350                v_align='top',
351                text=bui.Lstr(value=line),
352            )
353            voffs -= line_height

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:
355    @override
356    def get_main_window_state(self) -> bui.MainWindowState:
357        # Support recreating our window for back/refresh purposes.
358        cls = type(self)
359        return bui.BasicMainWindowState(
360            create_call=lambda transition, origin_widget: cls(
361                transition=transition, origin_widget=origin_widget
362            )
363        )

Return a WindowState to recreate this window, if supported.