bauiv1lib.creditslist

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

Window for displaying game credits.

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