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

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

Return a WindowState to recreate this window, if supported.