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.
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.