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 logging
  9from typing import TYPE_CHECKING, override
 10
 11import bauiv1 as bui
 12
 13if TYPE_CHECKING:
 14    from typing import Sequence
 15
 16
 17class CreditsWindow(bui.MainWindow):
 18    """Window for displaying game credits."""
 19
 20    def __init__(
 21        self,
 22        transition: str | None = 'in_right',
 23        origin_widget: bui.Widget | None = None,
 24    ):
 25        # pylint: disable=too-many-locals
 26        # pylint: disable=too-many-statements
 27        import json
 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        )
111
112        bui.widget(
113            edit=scroll,
114            right_widget=bui.get_special_widget('squad_button'),
115        )
116        if uiscale is bui.UIScale.SMALL:
117            bui.widget(
118                edit=scroll,
119                left_widget=bui.get_special_widget('back_button'),
120            )
121
122        def _format_names(names2: Sequence[str], inset: float) -> str:
123            sval = ''
124            # measure a series since there's overlaps and stuff..
125            space_width = (
126                bui.get_string_width(' ' * 10, suppress_warning=True) / 10.0
127            )
128            spacing = 330.0
129            col1 = inset
130            col2 = col1 + spacing
131            col3 = col2 + spacing
132            line_width = 0.0
133            nline = ''
134            for name in names2:
135                # move to the next column (or row) and print
136                if line_width > col3:
137                    sval += nline + '\n'
138                    nline = ''
139                    line_width = 0
140
141                if line_width > col2:
142                    target = col3
143                elif line_width > col1:
144                    target = col2
145                else:
146                    target = col1
147                spacingstr = ' ' * int((target - line_width) / space_width)
148                nline += spacingstr
149                nline += name
150                line_width = bui.get_string_width(nline, suppress_warning=True)
151            if nline != '':
152                sval += nline + '\n'
153            return sval
154
155        sound_and_music = bui.Lstr(
156            resource=f'{self._r}.songCreditText'
157        ).evaluate()
158        sound_and_music = sound_and_music.replace(
159            '${TITLE}', "'William Tell (Trumpet Entry)'"
160        )
161        sound_and_music = sound_and_music.replace(
162            '${PERFORMER}', 'The Apollo Symphony Orchestra'
163        )
164        sound_and_music = sound_and_music.replace(
165            '${PERFORMER}', 'The Apollo Symphony Orchestra'
166        )
167        sound_and_music = sound_and_music.replace(
168            '${COMPOSER}', 'Gioacchino Rossini'
169        )
170        sound_and_music = sound_and_music.replace('${ARRANGER}', 'Chris Worth')
171        sound_and_music = sound_and_music.replace('${PUBLISHER}', 'BMI')
172        sound_and_music = sound_and_music.replace(
173            '${SOURCE}', 'www.AudioSparx.com'
174        )
175        spc = '     '
176        sound_and_music = spc + sound_and_music.replace('\n', '\n' + spc)
177        names = [
178            'HubOfTheUniverseProd',
179            'Jovica',
180            'LG',
181            'Leady',
182            'Percy Duke',
183            'PhreaKsAccount',
184            'Pogotron',
185            'Rock Savage',
186            'anamorphosis',
187            'benboncan',
188            'cdrk',
189            'chipfork',
190            'guitarguy1985',
191            'jascha',
192            'joedeshon',
193            'loofa',
194            'm_O_m',
195            'mich3d',
196            'sandyrb',
197            'shakaharu',
198            'sirplus',
199            'stickman',
200            'thanvannispen',
201            'virotic',
202            'zimbot',
203        ]
204        names.sort(key=lambda x: x.lower())
205        freesound_names = _format_names(names, 90)
206
207        try:
208            with open(
209                os.path.join(
210                    bui.app.env.data_directory,
211                    'ba_data',
212                    'data',
213                    'langdata.json',
214                ),
215                encoding='utf-8',
216            ) as infile:
217                translation_contributors = json.loads(infile.read())[
218                    'translation_contributors'
219                ]
220        except Exception:
221            logging.exception('Error reading translation contributors.')
222            translation_contributors = []
223
224        translation_names = _format_names(translation_contributors, 60)
225
226        # Need to bake this out and chop it up since we're passing our
227        # 65535 vertex limit for meshes..
228        # We can remove that limit once we drop support for GL ES2.. :-/
229        # (or add mesh splitting under the hood)
230        credits_text = (
231            '  '
232            + bui.Lstr(resource=f'{self._r}.codingGraphicsAudioText')
233            .evaluate()
234            .replace('${NAME}', 'Eric Froemling')
235            + '\n'
236            '\n'
237            '  '
238            + bui.Lstr(resource=f'{self._r}.additionalAudioArtIdeasText')
239            .evaluate()
240            .replace('${NAME}', 'Raphael Suter')
241            + '\n'
242            '\n'
243            '  '
244            + bui.Lstr(resource=f'{self._r}.soundAndMusicText').evaluate()
245            + '\n'
246            '\n' + sound_and_music + '\n'
247            '\n'
248            '     '
249            + bui.Lstr(resource=f'{self._r}.publicDomainMusicViaText')
250            .evaluate()
251            .replace('${NAME}', 'Musopen.com')
252            + '\n'
253            '        '
254            + bui.Lstr(resource=f'{self._r}.thanksEspeciallyToText')
255            .evaluate()
256            .replace('${NAME}', 'the US Army, Navy, and Marine Bands')
257            + '\n'
258            '\n'
259            '     '
260            + bui.Lstr(resource=f'{self._r}.additionalMusicFromText')
261            .evaluate()
262            .replace('${NAME}', 'The YouTube Audio Library')
263            + '\n'
264            '\n'
265            '     '
266            + bui.Lstr(resource=f'{self._r}.soundsText')
267            .evaluate()
268            .replace('${SOURCE}', 'Freesound.org')
269            + '\n'
270            '\n' + freesound_names + '\n'
271            '\n'
272            '  '
273            + bui.Lstr(
274                resource=f'{self._r}.languageTranslationsText'
275            ).evaluate()
276            + '\n'
277            '\n'
278            + '\n'.join(translation_names.splitlines()[:146])
279            + '\n'.join(translation_names.splitlines()[146:])
280            + '\n'
281            '\n'
282            '  Shout Out to Awesome Mods / Modders / Contributors:\n\n'
283            '     BombDash ModPack\n'
284            '     TheMikirog & SoK - BombSquad Joyride Modpack\n'
285            '     Mrmaxmeier - BombSquad-Community-Mod-Manager\n'
286            '     Ritiek Malhotra \n'
287            '     Dliwk\n'
288            '     vishal332008\n'
289            '     itsre3\n'
290            '     Drooopyyy\n'
291            '\n'
292            '  Holiday theme vector art designed by Freepik\n'
293            '\n'
294            '  '
295            + bui.Lstr(resource=f'{self._r}.specialThanksText').evaluate()
296            + '\n'
297            '\n'
298            '     Todd, Laura, and Robert Froemling\n'
299            '     '
300            + bui.Lstr(resource=f'{self._r}.allMyFamilyText')
301            .evaluate()
302            .replace('\n', '\n     ')
303            + '\n'
304            '     '
305            + bui.Lstr(
306                resource=f'{self._r}.whoeverInventedCoffeeText'
307            ).evaluate()
308            + '\n'
309            '\n'
310            '  ' + bui.Lstr(resource=f'{self._r}.legalText').evaluate() + '\n'
311            '\n'
312            '     '
313            + bui.Lstr(resource=f'{self._r}.softwareBasedOnText')
314            .evaluate()
315            .replace('${NAME}', 'the Khronos Group')
316            + '\n'
317            '\n'
318            '                                       '
319            '                      www.ballistica.net\n'
320        )
321
322        txt = credits_text
323        lines = txt.splitlines()
324        line_height = 20
325
326        scale = 0.55
327        self._sub_width = width - 80
328        self._sub_height = line_height * len(lines) + 40
329
330        container = self._subcontainer = bui.containerwidget(
331            parent=scroll,
332            size=(self._sub_width, self._sub_height),
333            background=False,
334            claims_left_right=False,
335            claims_tab=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):
 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        import json
 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        )
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            claims_tab=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)
 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        import json
 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        )
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            claims_tab=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.