bauiv1lib.gettokens
UI functionality for purchasing/acquiring currency.
1# Released under the MIT License. See LICENSE for details. 2# 3"""UI functionality for purchasing/acquiring currency.""" 4 5from __future__ import annotations 6 7import time 8from enum import Enum 9from functools import partial 10from dataclasses import dataclass 11from typing import TYPE_CHECKING, assert_never 12 13import bacommon.cloud 14import bauiv1 as bui 15 16 17if TYPE_CHECKING: 18 from typing import Any, Callable 19 20 21@dataclass 22class _ButtonDef: 23 itemid: str 24 width: float 25 color: tuple[float, float, float] 26 imgdefs: list[_ImgDef] 27 txtdefs: list[_TxtDef] 28 prepad: float = 0.0 29 30 31@dataclass 32class _ImgDef: 33 tex: str 34 pos: tuple[float, float] 35 size: tuple[float, float] 36 color: tuple[float, float, float] = (1, 1, 1) 37 opacity: float = 1.0 38 draw_controller_mult: float | None = None 39 40 41class TextContents(Enum): 42 """Some type of text to show.""" 43 44 PRICE = 'price' 45 46 47@dataclass 48class _TxtDef: 49 text: str | TextContents | bui.Lstr 50 pos: tuple[float, float] 51 maxwidth: float | None 52 scale: float = 1.0 53 color: tuple[float, float, float] = (1, 1, 1) 54 rotate: float | None = None 55 56 57class GetTokensWindow(bui.Window): 58 """Window for purchasing/acquiring classic tickets.""" 59 60 class State(Enum): 61 """What are we doing?""" 62 63 LOADING = 'loading' 64 NOT_SIGNED_IN = 'not_signed_in' 65 HAVE_GOLD_PASS = 'have_gold_pass' 66 SHOWING_STORE = 'showing_store' 67 68 def __init__( 69 self, 70 transition: str = 'in_right', 71 origin_widget: bui.Widget | None = None, 72 restore_previous_call: Callable[[bui.Widget], None] | None = None, 73 ): 74 # pylint: disable=too-many-locals 75 76 bwidthstd = 170 77 bwidthwide = 300 78 ycolor = (0, 0, 0.3) 79 pcolor = (0, 0, 0.3) 80 pos1 = 65 81 pos2 = 34 82 titlescale = 0.9 83 pricescale = 0.65 84 bcapcol1 = (0.25, 0.13, 0.02) 85 self._buttondefs: list[_ButtonDef] = [ 86 _ButtonDef( 87 itemid='tokens1', 88 width=bwidthstd, 89 color=ycolor, 90 imgdefs=[ 91 _ImgDef( 92 'tokens1', 93 pos=(-3, 85), 94 size=(172, 172), 95 opacity=1.0, 96 draw_controller_mult=0.5, 97 ), 98 _ImgDef( 99 'windowBottomCap', 100 pos=(1.5, 4), 101 size=(bwidthstd * 0.960, 100), 102 color=bcapcol1, 103 opacity=1.0, 104 ), 105 ], 106 txtdefs=[ 107 _TxtDef( 108 bui.Lstr( 109 resource='tokens.numTokensText', 110 subs=[('${COUNT}', '50')], 111 ), 112 pos=(bwidthstd * 0.5, pos1), 113 color=(1.1, 1.05, 1.0), 114 scale=titlescale, 115 maxwidth=bwidthstd * 0.9, 116 ), 117 _TxtDef( 118 TextContents.PRICE, 119 pos=(bwidthstd * 0.5, pos2), 120 color=(1.1, 1.05, 1.0), 121 scale=pricescale, 122 maxwidth=bwidthstd * 0.9, 123 ), 124 ], 125 ), 126 _ButtonDef( 127 itemid='tokens2', 128 width=bwidthstd, 129 color=ycolor, 130 imgdefs=[ 131 _ImgDef( 132 'tokens2', 133 pos=(-3, 85), 134 size=(172, 172), 135 opacity=1.0, 136 draw_controller_mult=0.5, 137 ), 138 _ImgDef( 139 'windowBottomCap', 140 pos=(1.5, 4), 141 size=(bwidthstd * 0.960, 100), 142 color=bcapcol1, 143 opacity=1.0, 144 ), 145 ], 146 txtdefs=[ 147 _TxtDef( 148 bui.Lstr( 149 resource='tokens.numTokensText', 150 subs=[('${COUNT}', '500')], 151 ), 152 pos=(bwidthstd * 0.5, pos1), 153 color=(1.1, 1.05, 1.0), 154 scale=titlescale, 155 maxwidth=bwidthstd * 0.9, 156 ), 157 _TxtDef( 158 TextContents.PRICE, 159 pos=(bwidthstd * 0.5, pos2), 160 color=(1.1, 1.05, 1.0), 161 scale=pricescale, 162 maxwidth=bwidthstd * 0.9, 163 ), 164 ], 165 ), 166 _ButtonDef( 167 itemid='tokens3', 168 width=bwidthstd, 169 color=ycolor, 170 imgdefs=[ 171 _ImgDef( 172 'tokens3', 173 pos=(-3, 85), 174 size=(172, 172), 175 opacity=1.0, 176 draw_controller_mult=0.5, 177 ), 178 _ImgDef( 179 'windowBottomCap', 180 pos=(1.5, 4), 181 size=(bwidthstd * 0.960, 100), 182 color=bcapcol1, 183 opacity=1.0, 184 ), 185 ], 186 txtdefs=[ 187 _TxtDef( 188 bui.Lstr( 189 resource='tokens.numTokensText', 190 subs=[('${COUNT}', '1200')], 191 ), 192 pos=(bwidthstd * 0.5, pos1), 193 color=(1.1, 1.05, 1.0), 194 scale=titlescale, 195 maxwidth=bwidthstd * 0.9, 196 ), 197 _TxtDef( 198 TextContents.PRICE, 199 pos=(bwidthstd * 0.5, pos2), 200 color=(1.1, 1.05, 1.0), 201 scale=pricescale, 202 maxwidth=bwidthstd * 0.9, 203 ), 204 ], 205 ), 206 _ButtonDef( 207 itemid='tokens4', 208 width=bwidthstd, 209 color=ycolor, 210 imgdefs=[ 211 _ImgDef( 212 'tokens4', 213 pos=(-3, 85), 214 size=(172, 172), 215 opacity=1.0, 216 draw_controller_mult=0.5, 217 ), 218 _ImgDef( 219 'windowBottomCap', 220 pos=(1.5, 4), 221 size=(bwidthstd * 0.960, 100), 222 color=bcapcol1, 223 opacity=1.0, 224 ), 225 ], 226 txtdefs=[ 227 _TxtDef( 228 bui.Lstr( 229 resource='tokens.numTokensText', 230 subs=[('${COUNT}', '2600')], 231 ), 232 pos=(bwidthstd * 0.5, pos1), 233 color=(1.1, 1.05, 1.0), 234 scale=titlescale, 235 maxwidth=bwidthstd * 0.9, 236 ), 237 _TxtDef( 238 TextContents.PRICE, 239 pos=(bwidthstd * 0.5, pos2), 240 color=(1.1, 1.05, 1.0), 241 scale=pricescale, 242 maxwidth=bwidthstd * 0.9, 243 ), 244 ], 245 ), 246 _ButtonDef( 247 itemid='gold_pass', 248 width=bwidthwide, 249 color=pcolor, 250 imgdefs=[ 251 _ImgDef( 252 'goldPass', 253 pos=(-7, 102), 254 size=(312, 156), 255 draw_controller_mult=0.3, 256 ), 257 _ImgDef( 258 'windowBottomCap', 259 pos=(8, 4), 260 size=(bwidthwide * 0.923, 116), 261 color=(0.25, 0.12, 0.15), 262 opacity=1.0, 263 ), 264 ], 265 txtdefs=[ 266 _TxtDef( 267 bui.Lstr(resource='goldPass.goldPassText'), 268 pos=(bwidthwide * 0.5, pos1 + 27), 269 color=(1.1, 1.05, 1.0), 270 scale=titlescale, 271 maxwidth=bwidthwide * 0.8, 272 ), 273 _TxtDef( 274 bui.Lstr(resource='goldPass.desc1InfTokensText'), 275 pos=(bwidthwide * 0.5, pos1 + 6), 276 color=(1.1, 1.05, 1.0), 277 scale=0.4, 278 maxwidth=bwidthwide * 0.8, 279 ), 280 _TxtDef( 281 bui.Lstr(resource='goldPass.desc2NoAdsText'), 282 pos=(bwidthwide * 0.5, pos1 + 6 - 13 * 1), 283 color=(1.1, 1.05, 1.0), 284 scale=0.4, 285 maxwidth=bwidthwide * 0.8, 286 ), 287 _TxtDef( 288 bui.Lstr(resource='goldPass.desc3ForeverText'), 289 pos=(bwidthwide * 0.5, pos1 + 6 - 13 * 2), 290 color=(1.1, 1.05, 1.0), 291 scale=0.4, 292 maxwidth=bwidthwide * 0.8, 293 ), 294 _TxtDef( 295 TextContents.PRICE, 296 pos=(bwidthwide * 0.5, pos2 - 9), 297 color=(1.1, 1.05, 1.0), 298 scale=pricescale, 299 maxwidth=bwidthwide * 0.8, 300 ), 301 ], 302 prepad=-8, 303 ), 304 ] 305 306 self._transitioning_out = False 307 self._restore_previous_call = restore_previous_call 308 self._textcolor = (0.92, 0.92, 2.0) 309 310 self._query_in_flight = False 311 self._last_query_time = -1.0 312 self._last_query_response: bacommon.cloud.StoreQueryResponse | None = ( 313 None 314 ) 315 316 # If they provided an origin-widget, scale up from that. 317 scale_origin: tuple[float, float] | None 318 if origin_widget is not None: 319 self._transition_out = 'out_scale' 320 scale_origin = origin_widget.get_screen_space_center() 321 transition = 'in_scale' 322 else: 323 self._transition_out = 'out_right' 324 scale_origin = None 325 326 uiscale = bui.app.ui_v1.uiscale 327 self._width = 1000.0 if uiscale is bui.UIScale.SMALL else 800.0 328 self._x_inset = 25.0 if uiscale is bui.UIScale.SMALL else 0.0 329 self._height = 550 if uiscale is bui.UIScale.SMALL else 480.0 330 self._y_offset = -60 if uiscale is bui.UIScale.SMALL else 0 331 332 self._r = 'getTokensWindow' 333 334 super().__init__( 335 root_widget=bui.containerwidget( 336 size=(self._width, self._height), 337 transition=transition, 338 scale_origin_stack_offset=scale_origin, 339 color=(0.3, 0.23, 0.36), 340 scale=( 341 1.5 342 if uiscale is bui.UIScale.SMALL 343 else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0 344 ), 345 stack_offset=( 346 (0, -3) if uiscale is bui.UIScale.SMALL else (0, 0) 347 ), 348 # toolbar_visibility='menu_minimal', 349 toolbar_visibility='get_tokens', 350 ) 351 ) 352 353 if uiscale is bui.UIScale.SMALL: 354 bui.containerwidget( 355 edit=self._root_widget, on_cancel_call=self._back 356 ) 357 self._back_button = bui.get_special_widget('back_button') 358 else: 359 self._back_button = bui.buttonwidget( 360 parent=self._root_widget, 361 position=( 362 55 + self._x_inset, 363 self._height - 80 + self._y_offset, 364 ), 365 size=( 366 (140, 60) 367 if self._restore_previous_call is None 368 else (60, 60) 369 ), 370 scale=1.0, 371 autoselect=True, 372 label=( 373 bui.Lstr(resource='doneText') 374 if self._restore_previous_call is None 375 else bui.charstr(bui.SpecialChar.BACK) 376 ), 377 button_type=( 378 'regular' 379 if self._restore_previous_call is None 380 else 'backSmall' 381 ), 382 on_activate_call=self._back, 383 ) 384 # if uiscale is bui.UIScale.SMALL: 385 # bui.widget( 386 # edit=self._back_button, 387 # up_widget=bui.get_special_widget('tokens_meter'), 388 # ) 389 bui.containerwidget( 390 edit=self._root_widget, cancel_button=self._back_button 391 ) 392 393 self._title_text = bui.textwidget( 394 parent=self._root_widget, 395 position=(self._width * 0.5, self._height - 42 + self._y_offset), 396 size=(0, 0), 397 color=self._textcolor, 398 flatness=0.0, 399 shadow=1.0, 400 scale=1.2, 401 h_align='center', 402 v_align='center', 403 text=bui.Lstr(resource='tokens.getTokensText'), 404 maxwidth=260, 405 ) 406 407 self._status_text = bui.textwidget( 408 parent=self._root_widget, 409 size=(0, 0), 410 position=(self._width * 0.5, self._height * 0.5), 411 h_align='center', 412 v_align='center', 413 color=(0.6, 0.6, 0.6), 414 scale=0.75, 415 text=bui.Lstr(resource='store.loadingText'), 416 ) 417 418 self._core_widgets = [ 419 self._back_button, 420 self._title_text, 421 self._status_text, 422 ] 423 424 # self._token_count_widget: bui.Widget | None = None 425 # self._smooth_update_timer: bui.AppTimer | None = None 426 # self._smooth_token_count: float | None = None 427 # self._token_count: int = 0 428 # self._smooth_increase_speed = 1.0 429 # self._ticking_sound: bui.Sound | None = None 430 431 # Get all textures used by our buttons preloading so hopefully 432 # they'll be in place by the time we show them. 433 for bdef in self._buttondefs: 434 for bimg in bdef.imgdefs: 435 bui.gettexture(bimg.tex) 436 437 self._state = self.State.LOADING 438 439 self._update_timer = bui.AppTimer( 440 0.789, bui.WeakCall(self._update), repeat=True 441 ) 442 self._update() 443 444 # def __del__(self) -> None: 445 # if self._ticking_sound is not None: 446 # self._ticking_sound.stop() 447 # self._ticking_sound = None 448 449 def _update(self) -> None: 450 # No-op if our underlying widget is dead or on its way out. 451 if not self._root_widget or self._root_widget.transitioning_out: 452 return 453 454 plus = bui.app.plus 455 456 if plus is None or plus.accounts.primary is None: 457 self._update_state(self.State.NOT_SIGNED_IN) 458 return 459 460 # Poll for relevant changes to the store or our account. 461 now = time.monotonic() 462 if not self._query_in_flight and now - self._last_query_time > 2.0: 463 self._last_query_time = now 464 self._query_in_flight = True 465 with plus.accounts.primary: 466 plus.cloud.send_message_cb( 467 bacommon.cloud.StoreQueryMessage(), 468 on_response=bui.WeakCall(self._on_store_query_response), 469 ) 470 471 # Can't do much until we get a store state. 472 if self._last_query_response is None: 473 return 474 475 # If we've got a gold-pass, just show that. No need to offer any 476 # other purchases. 477 if self._last_query_response.gold_pass: 478 self._update_state(self.State.HAVE_GOLD_PASS) 479 return 480 481 # Ok we seem to be signed in and have store stuff we can show. 482 # Do that. 483 self._update_state(self.State.SHOWING_STORE) 484 485 def _update_state(self, state: State) -> None: 486 487 # We don't do much when state is unchanged. 488 if state is self._state: 489 # Update a few things in store mode though, such as token 490 # count. 491 if state is self.State.SHOWING_STORE: 492 self._update_store_state() 493 return 494 495 # Ok, state is changing. Start by resetting to a blank slate. 496 # self._token_count_widget = None 497 for widget in self._root_widget.get_children(): 498 if widget not in self._core_widgets: 499 widget.delete() 500 501 # Build up new state. 502 if state is self.State.NOT_SIGNED_IN: 503 bui.textwidget( 504 edit=self._status_text, 505 color=(1, 0, 0), 506 text=bui.Lstr(resource='notSignedInErrorText'), 507 ) 508 elif state is self.State.LOADING: 509 raise RuntimeError('Should never return to loading state.') 510 elif state is self.State.HAVE_GOLD_PASS: 511 bui.textwidget( 512 edit=self._status_text, 513 color=(0, 1, 0), 514 text=bui.Lstr(resource='tokens.youHaveGoldPassText'), 515 ) 516 elif state is self.State.SHOWING_STORE: 517 assert self._last_query_response is not None 518 bui.textwidget(edit=self._status_text, text='') 519 self._build_store_for_response(self._last_query_response) 520 else: 521 # Make sure we handle all cases. 522 assert_never(state) 523 524 self._state = state 525 526 def _on_load_error(self) -> None: 527 bui.textwidget( 528 edit=self._status_text, 529 text=bui.Lstr(resource='internal.unavailableNoConnectionText'), 530 color=(1, 0, 0), 531 ) 532 533 def _on_store_query_response( 534 self, response: bacommon.cloud.StoreQueryResponse | Exception 535 ) -> None: 536 self._query_in_flight = False 537 if isinstance(response, bacommon.cloud.StoreQueryResponse): 538 self._last_query_response = response 539 # Hurry along any effects of this response. 540 self._update() 541 542 def _build_store_for_response( 543 self, response: bacommon.cloud.StoreQueryResponse 544 ) -> None: 545 # pylint: disable=too-many-locals 546 plus = bui.app.plus 547 548 uiscale = bui.app.ui_v1.uiscale 549 550 bui.textwidget(edit=self._status_text, text='') 551 552 xinset = 40 553 554 scrollwidth = self._width - 2 * (self._x_inset + xinset) 555 scrollheight = 280 556 buttonpadding = -5 557 558 yoffs = 5 559 560 # We currently don't handle the zero-button case. 561 assert self._buttondefs 562 563 sidepad = 10.0 564 total_button_width = ( 565 sum(b.width + b.prepad for b in self._buttondefs) 566 + buttonpadding * (len(self._buttondefs) - 1) 567 + 2 * sidepad 568 ) 569 570 h_scroll = bui.hscrollwidget( 571 parent=self._root_widget, 572 size=(scrollwidth, scrollheight), 573 position=( 574 self._x_inset + xinset, 575 self._height - 415 + self._y_offset, 576 ), 577 claims_left_right=True, 578 highlight=False, 579 border_opacity=0.3 if uiscale is bui.UIScale.SMALL else 1.0, 580 ) 581 subcontainer = bui.containerwidget( 582 parent=h_scroll, 583 background=False, 584 size=(max(total_button_width, scrollwidth), scrollheight), 585 ) 586 tinfobtn = bui.buttonwidget( 587 parent=self._root_widget, 588 autoselect=True, 589 label=bui.Lstr(resource='learnMoreText'), 590 position=( 591 self._width * 0.5 - 75, 592 self._height - 125 + self._y_offset, 593 ), 594 size=(180, 43), 595 scale=0.8, 596 color=(0.4, 0.25, 0.5), 597 textcolor=self._textcolor, 598 on_activate_call=partial( 599 self._on_learn_more_press, response.token_info_url 600 ), 601 ) 602 if uiscale is bui.UIScale.SMALL: 603 bui.widget( 604 edit=tinfobtn, 605 left_widget=bui.get_special_widget('back_button'), 606 up_widget=bui.get_special_widget('back_button'), 607 ) 608 609 bui.widget( 610 edit=tinfobtn, 611 right_widget=bui.get_special_widget('tokens_meter'), 612 ) 613 614 x = sidepad 615 bwidgets: list[bui.Widget] = [] 616 for i, buttondef in enumerate(self._buttondefs): 617 618 price = None if plus is None else plus.get_price(buttondef.itemid) 619 620 x += buttondef.prepad 621 tdelay = 0.3 - i / len(self._buttondefs) * 0.25 622 btn = bui.buttonwidget( 623 autoselect=True, 624 label='', 625 color=buttondef.color, 626 transition_delay=tdelay, 627 up_widget=tinfobtn, 628 parent=subcontainer, 629 size=(buttondef.width, 275), 630 position=(x, -10 + yoffs), 631 button_type='square', 632 on_activate_call=partial( 633 self._purchase_press, buttondef.itemid 634 ), 635 ) 636 bwidgets.append(btn) 637 638 if i == 0: 639 bui.widget(edit=btn, left_widget=self._back_button) 640 641 for imgdef in buttondef.imgdefs: 642 _img = bui.imagewidget( 643 parent=subcontainer, 644 size=imgdef.size, 645 position=(x + imgdef.pos[0], imgdef.pos[1] + yoffs), 646 draw_controller=btn, 647 draw_controller_mult=imgdef.draw_controller_mult, 648 color=imgdef.color, 649 texture=bui.gettexture(imgdef.tex), 650 transition_delay=tdelay, 651 opacity=imgdef.opacity, 652 ) 653 for txtdef in buttondef.txtdefs: 654 txt: bui.Lstr | str 655 if isinstance(txtdef.text, TextContents): 656 if txtdef.text is TextContents.PRICE: 657 tcolor = ( 658 (1, 1, 1, 0.5) if price is None else txtdef.color 659 ) 660 txt = ( 661 bui.Lstr(resource='unavailableText') 662 if price is None 663 else price 664 ) 665 else: 666 # Make sure we cover all cases. 667 assert_never(txtdef.text) 668 else: 669 tcolor = txtdef.color 670 txt = txtdef.text 671 _txt = bui.textwidget( 672 parent=subcontainer, 673 text=txt, 674 position=(x + txtdef.pos[0], txtdef.pos[1] + yoffs), 675 size=(0, 0), 676 scale=txtdef.scale, 677 h_align='center', 678 v_align='center', 679 draw_controller=btn, 680 color=tcolor, 681 transition_delay=tdelay, 682 flatness=0.0, 683 shadow=1.0, 684 rotate=txtdef.rotate, 685 maxwidth=txtdef.maxwidth, 686 ) 687 x += buttondef.width + buttonpadding 688 bui.containerwidget(edit=subcontainer, visible_child=bwidgets[0]) 689 690 _tinfotxt = bui.textwidget( 691 parent=self._root_widget, 692 position=(self._width * 0.5, self._height - 70 + self._y_offset), 693 color=self._textcolor, 694 shadow=1.0, 695 scale=0.7, 696 size=(0, 0), 697 h_align='center', 698 v_align='center', 699 text=bui.Lstr(resource='tokens.shinyNewCurrencyText'), 700 ) 701 # self._token_count_widget = bui.textwidget( 702 # parent=self._root_widget, 703 # position=( 704 # self._width - self._x_inset - 120.0, 705 # self._height - 48 + self._y_offset, 706 # ), 707 # color=(2.0, 0.7, 0.0), 708 # shadow=1.0, 709 # flatness=0.0, 710 # size=(0, 0), 711 # h_align='left', 712 # v_align='center', 713 # text='', 714 # ) 715 # self._token_count = response.tokens 716 # self._smooth_token_count = float(self._token_count) 717 # self._smooth_update() # will set the text widget. 718 719 # _tlabeltxt = bui.textwidget( 720 # parent=self._root_widget, 721 # position=( 722 # self._width - self._x_inset - 123.0, 723 # self._height - 48 + self._y_offset, 724 # ), 725 # size=(0, 0), 726 # h_align='right', 727 # v_align='center', 728 # text=bui.charstr(bui.SpecialChar.TOKEN), 729 # ) 730 731 def _purchase_press(self, itemid: str) -> None: 732 plus = bui.app.plus 733 734 price = None if plus is None else plus.get_price(itemid) 735 736 if price is None: 737 if plus is not None and plus.supports_purchases(): 738 # Looks like internet is down or something temporary. 739 errmsg = bui.Lstr(resource='purchaseNotAvailableText') 740 else: 741 # Looks like purchases will never work here. 742 errmsg = bui.Lstr(resource='purchaseNeverAvailableText') 743 744 bui.screenmessage(errmsg, color=(1, 0.5, 0)) 745 bui.getsound('error').play() 746 return 747 748 assert plus is not None 749 plus.purchase(itemid) 750 751 def _update_store_state(self) -> None: 752 """Called to make minor updates to an already shown store.""" 753 # assert self._token_count_widget is not None 754 assert self._last_query_response is not None 755 756 # self._token_count = self._last_query_response.tokens 757 758 # Kick off new smooth update if need be. 759 # assert self._smooth_token_count is not None 760 # if ( 761 # self._token_count != int(self._smooth_token_count) 762 # and self._smooth_update_timer is None 763 # ): 764 # self._smooth_update_timer = bui.AppTimer( 765 # 0.05, bui.WeakCall(self._smooth_update), repeat=True 766 # ) 767 # diff = abs(float(self._token_count) - self._smooth_token_count) 768 # self._smooth_increase_speed = ( 769 # diff / 100.0 770 # if diff >= 5000 771 # else ( 772 # diff / 50.0 773 # if diff >= 1500 774 # else diff / 30.0 if diff >= 500 else diff / 15.0 775 # ) 776 # ) 777 778 # def _smooth_update(self) -> None: 779 780 # # Stop if the count widget disappears. 781 # if not self._token_count_widget: 782 # self._smooth_update_timer = None 783 # return 784 785 # finished = False 786 787 # # If we're going down, do it immediately. 788 # assert self._smooth_token_count is not None 789 # if int(self._smooth_token_count) >= self._token_count: 790 # self._smooth_token_count = float(self._token_count) 791 # finished = True 792 # else: 793 # # We're going up; start a sound if need be. 794 # self._smooth_token_count = min( 795 # self._smooth_token_count + 1.0 * self._smooth_increase_speed, 796 # self._token_count, 797 # ) 798 # if int(self._smooth_token_count) >= self._token_count: 799 # finished = True 800 # self._smooth_token_count = float(self._token_count) 801 # elif self._ticking_sound is None: 802 # self._ticking_sound = bui.getsound('scoreIncrease') 803 # self._ticking_sound.play() 804 805 # bui.textwidget( 806 # edit=self._token_count_widget, 807 # text=str(int(self._smooth_token_count)), 808 # ) 809 810 # # If we've reached the target, kill the timer/sound/etc. 811 # if finished: 812 # self._smooth_update_timer = None 813 # if self._ticking_sound is not None: 814 # self._ticking_sound.stop() 815 # self._ticking_sound = None 816 # bui.getsound('cashRegister2').play() 817 818 def _back(self) -> None: 819 820 # No-op if our underlying widget is dead or on its way out. 821 if not self._root_widget or self._root_widget.transitioning_out: 822 return 823 824 bui.containerwidget( 825 edit=self._root_widget, transition=self._transition_out 826 ) 827 if self._restore_previous_call is not None: 828 self._restore_previous_call(self._root_widget) 829 830 def _on_learn_more_press(self, url: str) -> None: 831 bui.open_url(url) 832 833 834def show_get_tokens_prompt() -> None: 835 """Show a 'not enough tokens' prompt with an option to purchase more. 836 837 Note that the purchase option may not always be available 838 depending on the build of the game. 839 """ 840 from bauiv1lib.confirm import ConfirmWindow 841 842 assert bui.app.classic is not None 843 844 # Currently always allowing token purchases. 845 if bool(True): 846 ConfirmWindow( 847 bui.Lstr(resource='tokens.notEnoughTokensText'), 848 GetTokensWindow, 849 ok_text=bui.Lstr(resource='tokens.getTokensText'), 850 width=460, 851 height=130, 852 ) 853 else: 854 ConfirmWindow( 855 bui.Lstr(resource='tokens.notEnoughTokensText'), 856 cancel_button=False, 857 width=460, 858 height=130, 859 )
class
TextContents(enum.Enum):
Some type of text to show.
PRICE =
<TextContents.PRICE: 'price'>
Inherited Members
- enum.Enum
- name
- value
class
GetTokensWindow(bauiv1._uitypes.Window):
58class GetTokensWindow(bui.Window): 59 """Window for purchasing/acquiring classic tickets.""" 60 61 class State(Enum): 62 """What are we doing?""" 63 64 LOADING = 'loading' 65 NOT_SIGNED_IN = 'not_signed_in' 66 HAVE_GOLD_PASS = 'have_gold_pass' 67 SHOWING_STORE = 'showing_store' 68 69 def __init__( 70 self, 71 transition: str = 'in_right', 72 origin_widget: bui.Widget | None = None, 73 restore_previous_call: Callable[[bui.Widget], None] | None = None, 74 ): 75 # pylint: disable=too-many-locals 76 77 bwidthstd = 170 78 bwidthwide = 300 79 ycolor = (0, 0, 0.3) 80 pcolor = (0, 0, 0.3) 81 pos1 = 65 82 pos2 = 34 83 titlescale = 0.9 84 pricescale = 0.65 85 bcapcol1 = (0.25, 0.13, 0.02) 86 self._buttondefs: list[_ButtonDef] = [ 87 _ButtonDef( 88 itemid='tokens1', 89 width=bwidthstd, 90 color=ycolor, 91 imgdefs=[ 92 _ImgDef( 93 'tokens1', 94 pos=(-3, 85), 95 size=(172, 172), 96 opacity=1.0, 97 draw_controller_mult=0.5, 98 ), 99 _ImgDef( 100 'windowBottomCap', 101 pos=(1.5, 4), 102 size=(bwidthstd * 0.960, 100), 103 color=bcapcol1, 104 opacity=1.0, 105 ), 106 ], 107 txtdefs=[ 108 _TxtDef( 109 bui.Lstr( 110 resource='tokens.numTokensText', 111 subs=[('${COUNT}', '50')], 112 ), 113 pos=(bwidthstd * 0.5, pos1), 114 color=(1.1, 1.05, 1.0), 115 scale=titlescale, 116 maxwidth=bwidthstd * 0.9, 117 ), 118 _TxtDef( 119 TextContents.PRICE, 120 pos=(bwidthstd * 0.5, pos2), 121 color=(1.1, 1.05, 1.0), 122 scale=pricescale, 123 maxwidth=bwidthstd * 0.9, 124 ), 125 ], 126 ), 127 _ButtonDef( 128 itemid='tokens2', 129 width=bwidthstd, 130 color=ycolor, 131 imgdefs=[ 132 _ImgDef( 133 'tokens2', 134 pos=(-3, 85), 135 size=(172, 172), 136 opacity=1.0, 137 draw_controller_mult=0.5, 138 ), 139 _ImgDef( 140 'windowBottomCap', 141 pos=(1.5, 4), 142 size=(bwidthstd * 0.960, 100), 143 color=bcapcol1, 144 opacity=1.0, 145 ), 146 ], 147 txtdefs=[ 148 _TxtDef( 149 bui.Lstr( 150 resource='tokens.numTokensText', 151 subs=[('${COUNT}', '500')], 152 ), 153 pos=(bwidthstd * 0.5, pos1), 154 color=(1.1, 1.05, 1.0), 155 scale=titlescale, 156 maxwidth=bwidthstd * 0.9, 157 ), 158 _TxtDef( 159 TextContents.PRICE, 160 pos=(bwidthstd * 0.5, pos2), 161 color=(1.1, 1.05, 1.0), 162 scale=pricescale, 163 maxwidth=bwidthstd * 0.9, 164 ), 165 ], 166 ), 167 _ButtonDef( 168 itemid='tokens3', 169 width=bwidthstd, 170 color=ycolor, 171 imgdefs=[ 172 _ImgDef( 173 'tokens3', 174 pos=(-3, 85), 175 size=(172, 172), 176 opacity=1.0, 177 draw_controller_mult=0.5, 178 ), 179 _ImgDef( 180 'windowBottomCap', 181 pos=(1.5, 4), 182 size=(bwidthstd * 0.960, 100), 183 color=bcapcol1, 184 opacity=1.0, 185 ), 186 ], 187 txtdefs=[ 188 _TxtDef( 189 bui.Lstr( 190 resource='tokens.numTokensText', 191 subs=[('${COUNT}', '1200')], 192 ), 193 pos=(bwidthstd * 0.5, pos1), 194 color=(1.1, 1.05, 1.0), 195 scale=titlescale, 196 maxwidth=bwidthstd * 0.9, 197 ), 198 _TxtDef( 199 TextContents.PRICE, 200 pos=(bwidthstd * 0.5, pos2), 201 color=(1.1, 1.05, 1.0), 202 scale=pricescale, 203 maxwidth=bwidthstd * 0.9, 204 ), 205 ], 206 ), 207 _ButtonDef( 208 itemid='tokens4', 209 width=bwidthstd, 210 color=ycolor, 211 imgdefs=[ 212 _ImgDef( 213 'tokens4', 214 pos=(-3, 85), 215 size=(172, 172), 216 opacity=1.0, 217 draw_controller_mult=0.5, 218 ), 219 _ImgDef( 220 'windowBottomCap', 221 pos=(1.5, 4), 222 size=(bwidthstd * 0.960, 100), 223 color=bcapcol1, 224 opacity=1.0, 225 ), 226 ], 227 txtdefs=[ 228 _TxtDef( 229 bui.Lstr( 230 resource='tokens.numTokensText', 231 subs=[('${COUNT}', '2600')], 232 ), 233 pos=(bwidthstd * 0.5, pos1), 234 color=(1.1, 1.05, 1.0), 235 scale=titlescale, 236 maxwidth=bwidthstd * 0.9, 237 ), 238 _TxtDef( 239 TextContents.PRICE, 240 pos=(bwidthstd * 0.5, pos2), 241 color=(1.1, 1.05, 1.0), 242 scale=pricescale, 243 maxwidth=bwidthstd * 0.9, 244 ), 245 ], 246 ), 247 _ButtonDef( 248 itemid='gold_pass', 249 width=bwidthwide, 250 color=pcolor, 251 imgdefs=[ 252 _ImgDef( 253 'goldPass', 254 pos=(-7, 102), 255 size=(312, 156), 256 draw_controller_mult=0.3, 257 ), 258 _ImgDef( 259 'windowBottomCap', 260 pos=(8, 4), 261 size=(bwidthwide * 0.923, 116), 262 color=(0.25, 0.12, 0.15), 263 opacity=1.0, 264 ), 265 ], 266 txtdefs=[ 267 _TxtDef( 268 bui.Lstr(resource='goldPass.goldPassText'), 269 pos=(bwidthwide * 0.5, pos1 + 27), 270 color=(1.1, 1.05, 1.0), 271 scale=titlescale, 272 maxwidth=bwidthwide * 0.8, 273 ), 274 _TxtDef( 275 bui.Lstr(resource='goldPass.desc1InfTokensText'), 276 pos=(bwidthwide * 0.5, pos1 + 6), 277 color=(1.1, 1.05, 1.0), 278 scale=0.4, 279 maxwidth=bwidthwide * 0.8, 280 ), 281 _TxtDef( 282 bui.Lstr(resource='goldPass.desc2NoAdsText'), 283 pos=(bwidthwide * 0.5, pos1 + 6 - 13 * 1), 284 color=(1.1, 1.05, 1.0), 285 scale=0.4, 286 maxwidth=bwidthwide * 0.8, 287 ), 288 _TxtDef( 289 bui.Lstr(resource='goldPass.desc3ForeverText'), 290 pos=(bwidthwide * 0.5, pos1 + 6 - 13 * 2), 291 color=(1.1, 1.05, 1.0), 292 scale=0.4, 293 maxwidth=bwidthwide * 0.8, 294 ), 295 _TxtDef( 296 TextContents.PRICE, 297 pos=(bwidthwide * 0.5, pos2 - 9), 298 color=(1.1, 1.05, 1.0), 299 scale=pricescale, 300 maxwidth=bwidthwide * 0.8, 301 ), 302 ], 303 prepad=-8, 304 ), 305 ] 306 307 self._transitioning_out = False 308 self._restore_previous_call = restore_previous_call 309 self._textcolor = (0.92, 0.92, 2.0) 310 311 self._query_in_flight = False 312 self._last_query_time = -1.0 313 self._last_query_response: bacommon.cloud.StoreQueryResponse | None = ( 314 None 315 ) 316 317 # If they provided an origin-widget, scale up from that. 318 scale_origin: tuple[float, float] | None 319 if origin_widget is not None: 320 self._transition_out = 'out_scale' 321 scale_origin = origin_widget.get_screen_space_center() 322 transition = 'in_scale' 323 else: 324 self._transition_out = 'out_right' 325 scale_origin = None 326 327 uiscale = bui.app.ui_v1.uiscale 328 self._width = 1000.0 if uiscale is bui.UIScale.SMALL else 800.0 329 self._x_inset = 25.0 if uiscale is bui.UIScale.SMALL else 0.0 330 self._height = 550 if uiscale is bui.UIScale.SMALL else 480.0 331 self._y_offset = -60 if uiscale is bui.UIScale.SMALL else 0 332 333 self._r = 'getTokensWindow' 334 335 super().__init__( 336 root_widget=bui.containerwidget( 337 size=(self._width, self._height), 338 transition=transition, 339 scale_origin_stack_offset=scale_origin, 340 color=(0.3, 0.23, 0.36), 341 scale=( 342 1.5 343 if uiscale is bui.UIScale.SMALL 344 else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0 345 ), 346 stack_offset=( 347 (0, -3) if uiscale is bui.UIScale.SMALL else (0, 0) 348 ), 349 # toolbar_visibility='menu_minimal', 350 toolbar_visibility='get_tokens', 351 ) 352 ) 353 354 if uiscale is bui.UIScale.SMALL: 355 bui.containerwidget( 356 edit=self._root_widget, on_cancel_call=self._back 357 ) 358 self._back_button = bui.get_special_widget('back_button') 359 else: 360 self._back_button = bui.buttonwidget( 361 parent=self._root_widget, 362 position=( 363 55 + self._x_inset, 364 self._height - 80 + self._y_offset, 365 ), 366 size=( 367 (140, 60) 368 if self._restore_previous_call is None 369 else (60, 60) 370 ), 371 scale=1.0, 372 autoselect=True, 373 label=( 374 bui.Lstr(resource='doneText') 375 if self._restore_previous_call is None 376 else bui.charstr(bui.SpecialChar.BACK) 377 ), 378 button_type=( 379 'regular' 380 if self._restore_previous_call is None 381 else 'backSmall' 382 ), 383 on_activate_call=self._back, 384 ) 385 # if uiscale is bui.UIScale.SMALL: 386 # bui.widget( 387 # edit=self._back_button, 388 # up_widget=bui.get_special_widget('tokens_meter'), 389 # ) 390 bui.containerwidget( 391 edit=self._root_widget, cancel_button=self._back_button 392 ) 393 394 self._title_text = bui.textwidget( 395 parent=self._root_widget, 396 position=(self._width * 0.5, self._height - 42 + self._y_offset), 397 size=(0, 0), 398 color=self._textcolor, 399 flatness=0.0, 400 shadow=1.0, 401 scale=1.2, 402 h_align='center', 403 v_align='center', 404 text=bui.Lstr(resource='tokens.getTokensText'), 405 maxwidth=260, 406 ) 407 408 self._status_text = bui.textwidget( 409 parent=self._root_widget, 410 size=(0, 0), 411 position=(self._width * 0.5, self._height * 0.5), 412 h_align='center', 413 v_align='center', 414 color=(0.6, 0.6, 0.6), 415 scale=0.75, 416 text=bui.Lstr(resource='store.loadingText'), 417 ) 418 419 self._core_widgets = [ 420 self._back_button, 421 self._title_text, 422 self._status_text, 423 ] 424 425 # self._token_count_widget: bui.Widget | None = None 426 # self._smooth_update_timer: bui.AppTimer | None = None 427 # self._smooth_token_count: float | None = None 428 # self._token_count: int = 0 429 # self._smooth_increase_speed = 1.0 430 # self._ticking_sound: bui.Sound | None = None 431 432 # Get all textures used by our buttons preloading so hopefully 433 # they'll be in place by the time we show them. 434 for bdef in self._buttondefs: 435 for bimg in bdef.imgdefs: 436 bui.gettexture(bimg.tex) 437 438 self._state = self.State.LOADING 439 440 self._update_timer = bui.AppTimer( 441 0.789, bui.WeakCall(self._update), repeat=True 442 ) 443 self._update() 444 445 # def __del__(self) -> None: 446 # if self._ticking_sound is not None: 447 # self._ticking_sound.stop() 448 # self._ticking_sound = None 449 450 def _update(self) -> None: 451 # No-op if our underlying widget is dead or on its way out. 452 if not self._root_widget or self._root_widget.transitioning_out: 453 return 454 455 plus = bui.app.plus 456 457 if plus is None or plus.accounts.primary is None: 458 self._update_state(self.State.NOT_SIGNED_IN) 459 return 460 461 # Poll for relevant changes to the store or our account. 462 now = time.monotonic() 463 if not self._query_in_flight and now - self._last_query_time > 2.0: 464 self._last_query_time = now 465 self._query_in_flight = True 466 with plus.accounts.primary: 467 plus.cloud.send_message_cb( 468 bacommon.cloud.StoreQueryMessage(), 469 on_response=bui.WeakCall(self._on_store_query_response), 470 ) 471 472 # Can't do much until we get a store state. 473 if self._last_query_response is None: 474 return 475 476 # If we've got a gold-pass, just show that. No need to offer any 477 # other purchases. 478 if self._last_query_response.gold_pass: 479 self._update_state(self.State.HAVE_GOLD_PASS) 480 return 481 482 # Ok we seem to be signed in and have store stuff we can show. 483 # Do that. 484 self._update_state(self.State.SHOWING_STORE) 485 486 def _update_state(self, state: State) -> None: 487 488 # We don't do much when state is unchanged. 489 if state is self._state: 490 # Update a few things in store mode though, such as token 491 # count. 492 if state is self.State.SHOWING_STORE: 493 self._update_store_state() 494 return 495 496 # Ok, state is changing. Start by resetting to a blank slate. 497 # self._token_count_widget = None 498 for widget in self._root_widget.get_children(): 499 if widget not in self._core_widgets: 500 widget.delete() 501 502 # Build up new state. 503 if state is self.State.NOT_SIGNED_IN: 504 bui.textwidget( 505 edit=self._status_text, 506 color=(1, 0, 0), 507 text=bui.Lstr(resource='notSignedInErrorText'), 508 ) 509 elif state is self.State.LOADING: 510 raise RuntimeError('Should never return to loading state.') 511 elif state is self.State.HAVE_GOLD_PASS: 512 bui.textwidget( 513 edit=self._status_text, 514 color=(0, 1, 0), 515 text=bui.Lstr(resource='tokens.youHaveGoldPassText'), 516 ) 517 elif state is self.State.SHOWING_STORE: 518 assert self._last_query_response is not None 519 bui.textwidget(edit=self._status_text, text='') 520 self._build_store_for_response(self._last_query_response) 521 else: 522 # Make sure we handle all cases. 523 assert_never(state) 524 525 self._state = state 526 527 def _on_load_error(self) -> None: 528 bui.textwidget( 529 edit=self._status_text, 530 text=bui.Lstr(resource='internal.unavailableNoConnectionText'), 531 color=(1, 0, 0), 532 ) 533 534 def _on_store_query_response( 535 self, response: bacommon.cloud.StoreQueryResponse | Exception 536 ) -> None: 537 self._query_in_flight = False 538 if isinstance(response, bacommon.cloud.StoreQueryResponse): 539 self._last_query_response = response 540 # Hurry along any effects of this response. 541 self._update() 542 543 def _build_store_for_response( 544 self, response: bacommon.cloud.StoreQueryResponse 545 ) -> None: 546 # pylint: disable=too-many-locals 547 plus = bui.app.plus 548 549 uiscale = bui.app.ui_v1.uiscale 550 551 bui.textwidget(edit=self._status_text, text='') 552 553 xinset = 40 554 555 scrollwidth = self._width - 2 * (self._x_inset + xinset) 556 scrollheight = 280 557 buttonpadding = -5 558 559 yoffs = 5 560 561 # We currently don't handle the zero-button case. 562 assert self._buttondefs 563 564 sidepad = 10.0 565 total_button_width = ( 566 sum(b.width + b.prepad for b in self._buttondefs) 567 + buttonpadding * (len(self._buttondefs) - 1) 568 + 2 * sidepad 569 ) 570 571 h_scroll = bui.hscrollwidget( 572 parent=self._root_widget, 573 size=(scrollwidth, scrollheight), 574 position=( 575 self._x_inset + xinset, 576 self._height - 415 + self._y_offset, 577 ), 578 claims_left_right=True, 579 highlight=False, 580 border_opacity=0.3 if uiscale is bui.UIScale.SMALL else 1.0, 581 ) 582 subcontainer = bui.containerwidget( 583 parent=h_scroll, 584 background=False, 585 size=(max(total_button_width, scrollwidth), scrollheight), 586 ) 587 tinfobtn = bui.buttonwidget( 588 parent=self._root_widget, 589 autoselect=True, 590 label=bui.Lstr(resource='learnMoreText'), 591 position=( 592 self._width * 0.5 - 75, 593 self._height - 125 + self._y_offset, 594 ), 595 size=(180, 43), 596 scale=0.8, 597 color=(0.4, 0.25, 0.5), 598 textcolor=self._textcolor, 599 on_activate_call=partial( 600 self._on_learn_more_press, response.token_info_url 601 ), 602 ) 603 if uiscale is bui.UIScale.SMALL: 604 bui.widget( 605 edit=tinfobtn, 606 left_widget=bui.get_special_widget('back_button'), 607 up_widget=bui.get_special_widget('back_button'), 608 ) 609 610 bui.widget( 611 edit=tinfobtn, 612 right_widget=bui.get_special_widget('tokens_meter'), 613 ) 614 615 x = sidepad 616 bwidgets: list[bui.Widget] = [] 617 for i, buttondef in enumerate(self._buttondefs): 618 619 price = None if plus is None else plus.get_price(buttondef.itemid) 620 621 x += buttondef.prepad 622 tdelay = 0.3 - i / len(self._buttondefs) * 0.25 623 btn = bui.buttonwidget( 624 autoselect=True, 625 label='', 626 color=buttondef.color, 627 transition_delay=tdelay, 628 up_widget=tinfobtn, 629 parent=subcontainer, 630 size=(buttondef.width, 275), 631 position=(x, -10 + yoffs), 632 button_type='square', 633 on_activate_call=partial( 634 self._purchase_press, buttondef.itemid 635 ), 636 ) 637 bwidgets.append(btn) 638 639 if i == 0: 640 bui.widget(edit=btn, left_widget=self._back_button) 641 642 for imgdef in buttondef.imgdefs: 643 _img = bui.imagewidget( 644 parent=subcontainer, 645 size=imgdef.size, 646 position=(x + imgdef.pos[0], imgdef.pos[1] + yoffs), 647 draw_controller=btn, 648 draw_controller_mult=imgdef.draw_controller_mult, 649 color=imgdef.color, 650 texture=bui.gettexture(imgdef.tex), 651 transition_delay=tdelay, 652 opacity=imgdef.opacity, 653 ) 654 for txtdef in buttondef.txtdefs: 655 txt: bui.Lstr | str 656 if isinstance(txtdef.text, TextContents): 657 if txtdef.text is TextContents.PRICE: 658 tcolor = ( 659 (1, 1, 1, 0.5) if price is None else txtdef.color 660 ) 661 txt = ( 662 bui.Lstr(resource='unavailableText') 663 if price is None 664 else price 665 ) 666 else: 667 # Make sure we cover all cases. 668 assert_never(txtdef.text) 669 else: 670 tcolor = txtdef.color 671 txt = txtdef.text 672 _txt = bui.textwidget( 673 parent=subcontainer, 674 text=txt, 675 position=(x + txtdef.pos[0], txtdef.pos[1] + yoffs), 676 size=(0, 0), 677 scale=txtdef.scale, 678 h_align='center', 679 v_align='center', 680 draw_controller=btn, 681 color=tcolor, 682 transition_delay=tdelay, 683 flatness=0.0, 684 shadow=1.0, 685 rotate=txtdef.rotate, 686 maxwidth=txtdef.maxwidth, 687 ) 688 x += buttondef.width + buttonpadding 689 bui.containerwidget(edit=subcontainer, visible_child=bwidgets[0]) 690 691 _tinfotxt = bui.textwidget( 692 parent=self._root_widget, 693 position=(self._width * 0.5, self._height - 70 + self._y_offset), 694 color=self._textcolor, 695 shadow=1.0, 696 scale=0.7, 697 size=(0, 0), 698 h_align='center', 699 v_align='center', 700 text=bui.Lstr(resource='tokens.shinyNewCurrencyText'), 701 ) 702 # self._token_count_widget = bui.textwidget( 703 # parent=self._root_widget, 704 # position=( 705 # self._width - self._x_inset - 120.0, 706 # self._height - 48 + self._y_offset, 707 # ), 708 # color=(2.0, 0.7, 0.0), 709 # shadow=1.0, 710 # flatness=0.0, 711 # size=(0, 0), 712 # h_align='left', 713 # v_align='center', 714 # text='', 715 # ) 716 # self._token_count = response.tokens 717 # self._smooth_token_count = float(self._token_count) 718 # self._smooth_update() # will set the text widget. 719 720 # _tlabeltxt = bui.textwidget( 721 # parent=self._root_widget, 722 # position=( 723 # self._width - self._x_inset - 123.0, 724 # self._height - 48 + self._y_offset, 725 # ), 726 # size=(0, 0), 727 # h_align='right', 728 # v_align='center', 729 # text=bui.charstr(bui.SpecialChar.TOKEN), 730 # ) 731 732 def _purchase_press(self, itemid: str) -> None: 733 plus = bui.app.plus 734 735 price = None if plus is None else plus.get_price(itemid) 736 737 if price is None: 738 if plus is not None and plus.supports_purchases(): 739 # Looks like internet is down or something temporary. 740 errmsg = bui.Lstr(resource='purchaseNotAvailableText') 741 else: 742 # Looks like purchases will never work here. 743 errmsg = bui.Lstr(resource='purchaseNeverAvailableText') 744 745 bui.screenmessage(errmsg, color=(1, 0.5, 0)) 746 bui.getsound('error').play() 747 return 748 749 assert plus is not None 750 plus.purchase(itemid) 751 752 def _update_store_state(self) -> None: 753 """Called to make minor updates to an already shown store.""" 754 # assert self._token_count_widget is not None 755 assert self._last_query_response is not None 756 757 # self._token_count = self._last_query_response.tokens 758 759 # Kick off new smooth update if need be. 760 # assert self._smooth_token_count is not None 761 # if ( 762 # self._token_count != int(self._smooth_token_count) 763 # and self._smooth_update_timer is None 764 # ): 765 # self._smooth_update_timer = bui.AppTimer( 766 # 0.05, bui.WeakCall(self._smooth_update), repeat=True 767 # ) 768 # diff = abs(float(self._token_count) - self._smooth_token_count) 769 # self._smooth_increase_speed = ( 770 # diff / 100.0 771 # if diff >= 5000 772 # else ( 773 # diff / 50.0 774 # if diff >= 1500 775 # else diff / 30.0 if diff >= 500 else diff / 15.0 776 # ) 777 # ) 778 779 # def _smooth_update(self) -> None: 780 781 # # Stop if the count widget disappears. 782 # if not self._token_count_widget: 783 # self._smooth_update_timer = None 784 # return 785 786 # finished = False 787 788 # # If we're going down, do it immediately. 789 # assert self._smooth_token_count is not None 790 # if int(self._smooth_token_count) >= self._token_count: 791 # self._smooth_token_count = float(self._token_count) 792 # finished = True 793 # else: 794 # # We're going up; start a sound if need be. 795 # self._smooth_token_count = min( 796 # self._smooth_token_count + 1.0 * self._smooth_increase_speed, 797 # self._token_count, 798 # ) 799 # if int(self._smooth_token_count) >= self._token_count: 800 # finished = True 801 # self._smooth_token_count = float(self._token_count) 802 # elif self._ticking_sound is None: 803 # self._ticking_sound = bui.getsound('scoreIncrease') 804 # self._ticking_sound.play() 805 806 # bui.textwidget( 807 # edit=self._token_count_widget, 808 # text=str(int(self._smooth_token_count)), 809 # ) 810 811 # # If we've reached the target, kill the timer/sound/etc. 812 # if finished: 813 # self._smooth_update_timer = None 814 # if self._ticking_sound is not None: 815 # self._ticking_sound.stop() 816 # self._ticking_sound = None 817 # bui.getsound('cashRegister2').play() 818 819 def _back(self) -> None: 820 821 # No-op if our underlying widget is dead or on its way out. 822 if not self._root_widget or self._root_widget.transitioning_out: 823 return 824 825 bui.containerwidget( 826 edit=self._root_widget, transition=self._transition_out 827 ) 828 if self._restore_previous_call is not None: 829 self._restore_previous_call(self._root_widget) 830 831 def _on_learn_more_press(self, url: str) -> None: 832 bui.open_url(url)
Window for purchasing/acquiring classic tickets.
GetTokensWindow( transition: str = 'in_right', origin_widget: _bauiv1.Widget | None = None, restore_previous_call: Optional[Callable[[_bauiv1.Widget], NoneType]] = None)
69 def __init__( 70 self, 71 transition: str = 'in_right', 72 origin_widget: bui.Widget | None = None, 73 restore_previous_call: Callable[[bui.Widget], None] | None = None, 74 ): 75 # pylint: disable=too-many-locals 76 77 bwidthstd = 170 78 bwidthwide = 300 79 ycolor = (0, 0, 0.3) 80 pcolor = (0, 0, 0.3) 81 pos1 = 65 82 pos2 = 34 83 titlescale = 0.9 84 pricescale = 0.65 85 bcapcol1 = (0.25, 0.13, 0.02) 86 self._buttondefs: list[_ButtonDef] = [ 87 _ButtonDef( 88 itemid='tokens1', 89 width=bwidthstd, 90 color=ycolor, 91 imgdefs=[ 92 _ImgDef( 93 'tokens1', 94 pos=(-3, 85), 95 size=(172, 172), 96 opacity=1.0, 97 draw_controller_mult=0.5, 98 ), 99 _ImgDef( 100 'windowBottomCap', 101 pos=(1.5, 4), 102 size=(bwidthstd * 0.960, 100), 103 color=bcapcol1, 104 opacity=1.0, 105 ), 106 ], 107 txtdefs=[ 108 _TxtDef( 109 bui.Lstr( 110 resource='tokens.numTokensText', 111 subs=[('${COUNT}', '50')], 112 ), 113 pos=(bwidthstd * 0.5, pos1), 114 color=(1.1, 1.05, 1.0), 115 scale=titlescale, 116 maxwidth=bwidthstd * 0.9, 117 ), 118 _TxtDef( 119 TextContents.PRICE, 120 pos=(bwidthstd * 0.5, pos2), 121 color=(1.1, 1.05, 1.0), 122 scale=pricescale, 123 maxwidth=bwidthstd * 0.9, 124 ), 125 ], 126 ), 127 _ButtonDef( 128 itemid='tokens2', 129 width=bwidthstd, 130 color=ycolor, 131 imgdefs=[ 132 _ImgDef( 133 'tokens2', 134 pos=(-3, 85), 135 size=(172, 172), 136 opacity=1.0, 137 draw_controller_mult=0.5, 138 ), 139 _ImgDef( 140 'windowBottomCap', 141 pos=(1.5, 4), 142 size=(bwidthstd * 0.960, 100), 143 color=bcapcol1, 144 opacity=1.0, 145 ), 146 ], 147 txtdefs=[ 148 _TxtDef( 149 bui.Lstr( 150 resource='tokens.numTokensText', 151 subs=[('${COUNT}', '500')], 152 ), 153 pos=(bwidthstd * 0.5, pos1), 154 color=(1.1, 1.05, 1.0), 155 scale=titlescale, 156 maxwidth=bwidthstd * 0.9, 157 ), 158 _TxtDef( 159 TextContents.PRICE, 160 pos=(bwidthstd * 0.5, pos2), 161 color=(1.1, 1.05, 1.0), 162 scale=pricescale, 163 maxwidth=bwidthstd * 0.9, 164 ), 165 ], 166 ), 167 _ButtonDef( 168 itemid='tokens3', 169 width=bwidthstd, 170 color=ycolor, 171 imgdefs=[ 172 _ImgDef( 173 'tokens3', 174 pos=(-3, 85), 175 size=(172, 172), 176 opacity=1.0, 177 draw_controller_mult=0.5, 178 ), 179 _ImgDef( 180 'windowBottomCap', 181 pos=(1.5, 4), 182 size=(bwidthstd * 0.960, 100), 183 color=bcapcol1, 184 opacity=1.0, 185 ), 186 ], 187 txtdefs=[ 188 _TxtDef( 189 bui.Lstr( 190 resource='tokens.numTokensText', 191 subs=[('${COUNT}', '1200')], 192 ), 193 pos=(bwidthstd * 0.5, pos1), 194 color=(1.1, 1.05, 1.0), 195 scale=titlescale, 196 maxwidth=bwidthstd * 0.9, 197 ), 198 _TxtDef( 199 TextContents.PRICE, 200 pos=(bwidthstd * 0.5, pos2), 201 color=(1.1, 1.05, 1.0), 202 scale=pricescale, 203 maxwidth=bwidthstd * 0.9, 204 ), 205 ], 206 ), 207 _ButtonDef( 208 itemid='tokens4', 209 width=bwidthstd, 210 color=ycolor, 211 imgdefs=[ 212 _ImgDef( 213 'tokens4', 214 pos=(-3, 85), 215 size=(172, 172), 216 opacity=1.0, 217 draw_controller_mult=0.5, 218 ), 219 _ImgDef( 220 'windowBottomCap', 221 pos=(1.5, 4), 222 size=(bwidthstd * 0.960, 100), 223 color=bcapcol1, 224 opacity=1.0, 225 ), 226 ], 227 txtdefs=[ 228 _TxtDef( 229 bui.Lstr( 230 resource='tokens.numTokensText', 231 subs=[('${COUNT}', '2600')], 232 ), 233 pos=(bwidthstd * 0.5, pos1), 234 color=(1.1, 1.05, 1.0), 235 scale=titlescale, 236 maxwidth=bwidthstd * 0.9, 237 ), 238 _TxtDef( 239 TextContents.PRICE, 240 pos=(bwidthstd * 0.5, pos2), 241 color=(1.1, 1.05, 1.0), 242 scale=pricescale, 243 maxwidth=bwidthstd * 0.9, 244 ), 245 ], 246 ), 247 _ButtonDef( 248 itemid='gold_pass', 249 width=bwidthwide, 250 color=pcolor, 251 imgdefs=[ 252 _ImgDef( 253 'goldPass', 254 pos=(-7, 102), 255 size=(312, 156), 256 draw_controller_mult=0.3, 257 ), 258 _ImgDef( 259 'windowBottomCap', 260 pos=(8, 4), 261 size=(bwidthwide * 0.923, 116), 262 color=(0.25, 0.12, 0.15), 263 opacity=1.0, 264 ), 265 ], 266 txtdefs=[ 267 _TxtDef( 268 bui.Lstr(resource='goldPass.goldPassText'), 269 pos=(bwidthwide * 0.5, pos1 + 27), 270 color=(1.1, 1.05, 1.0), 271 scale=titlescale, 272 maxwidth=bwidthwide * 0.8, 273 ), 274 _TxtDef( 275 bui.Lstr(resource='goldPass.desc1InfTokensText'), 276 pos=(bwidthwide * 0.5, pos1 + 6), 277 color=(1.1, 1.05, 1.0), 278 scale=0.4, 279 maxwidth=bwidthwide * 0.8, 280 ), 281 _TxtDef( 282 bui.Lstr(resource='goldPass.desc2NoAdsText'), 283 pos=(bwidthwide * 0.5, pos1 + 6 - 13 * 1), 284 color=(1.1, 1.05, 1.0), 285 scale=0.4, 286 maxwidth=bwidthwide * 0.8, 287 ), 288 _TxtDef( 289 bui.Lstr(resource='goldPass.desc3ForeverText'), 290 pos=(bwidthwide * 0.5, pos1 + 6 - 13 * 2), 291 color=(1.1, 1.05, 1.0), 292 scale=0.4, 293 maxwidth=bwidthwide * 0.8, 294 ), 295 _TxtDef( 296 TextContents.PRICE, 297 pos=(bwidthwide * 0.5, pos2 - 9), 298 color=(1.1, 1.05, 1.0), 299 scale=pricescale, 300 maxwidth=bwidthwide * 0.8, 301 ), 302 ], 303 prepad=-8, 304 ), 305 ] 306 307 self._transitioning_out = False 308 self._restore_previous_call = restore_previous_call 309 self._textcolor = (0.92, 0.92, 2.0) 310 311 self._query_in_flight = False 312 self._last_query_time = -1.0 313 self._last_query_response: bacommon.cloud.StoreQueryResponse | None = ( 314 None 315 ) 316 317 # If they provided an origin-widget, scale up from that. 318 scale_origin: tuple[float, float] | None 319 if origin_widget is not None: 320 self._transition_out = 'out_scale' 321 scale_origin = origin_widget.get_screen_space_center() 322 transition = 'in_scale' 323 else: 324 self._transition_out = 'out_right' 325 scale_origin = None 326 327 uiscale = bui.app.ui_v1.uiscale 328 self._width = 1000.0 if uiscale is bui.UIScale.SMALL else 800.0 329 self._x_inset = 25.0 if uiscale is bui.UIScale.SMALL else 0.0 330 self._height = 550 if uiscale is bui.UIScale.SMALL else 480.0 331 self._y_offset = -60 if uiscale is bui.UIScale.SMALL else 0 332 333 self._r = 'getTokensWindow' 334 335 super().__init__( 336 root_widget=bui.containerwidget( 337 size=(self._width, self._height), 338 transition=transition, 339 scale_origin_stack_offset=scale_origin, 340 color=(0.3, 0.23, 0.36), 341 scale=( 342 1.5 343 if uiscale is bui.UIScale.SMALL 344 else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0 345 ), 346 stack_offset=( 347 (0, -3) if uiscale is bui.UIScale.SMALL else (0, 0) 348 ), 349 # toolbar_visibility='menu_minimal', 350 toolbar_visibility='get_tokens', 351 ) 352 ) 353 354 if uiscale is bui.UIScale.SMALL: 355 bui.containerwidget( 356 edit=self._root_widget, on_cancel_call=self._back 357 ) 358 self._back_button = bui.get_special_widget('back_button') 359 else: 360 self._back_button = bui.buttonwidget( 361 parent=self._root_widget, 362 position=( 363 55 + self._x_inset, 364 self._height - 80 + self._y_offset, 365 ), 366 size=( 367 (140, 60) 368 if self._restore_previous_call is None 369 else (60, 60) 370 ), 371 scale=1.0, 372 autoselect=True, 373 label=( 374 bui.Lstr(resource='doneText') 375 if self._restore_previous_call is None 376 else bui.charstr(bui.SpecialChar.BACK) 377 ), 378 button_type=( 379 'regular' 380 if self._restore_previous_call is None 381 else 'backSmall' 382 ), 383 on_activate_call=self._back, 384 ) 385 # if uiscale is bui.UIScale.SMALL: 386 # bui.widget( 387 # edit=self._back_button, 388 # up_widget=bui.get_special_widget('tokens_meter'), 389 # ) 390 bui.containerwidget( 391 edit=self._root_widget, cancel_button=self._back_button 392 ) 393 394 self._title_text = bui.textwidget( 395 parent=self._root_widget, 396 position=(self._width * 0.5, self._height - 42 + self._y_offset), 397 size=(0, 0), 398 color=self._textcolor, 399 flatness=0.0, 400 shadow=1.0, 401 scale=1.2, 402 h_align='center', 403 v_align='center', 404 text=bui.Lstr(resource='tokens.getTokensText'), 405 maxwidth=260, 406 ) 407 408 self._status_text = bui.textwidget( 409 parent=self._root_widget, 410 size=(0, 0), 411 position=(self._width * 0.5, self._height * 0.5), 412 h_align='center', 413 v_align='center', 414 color=(0.6, 0.6, 0.6), 415 scale=0.75, 416 text=bui.Lstr(resource='store.loadingText'), 417 ) 418 419 self._core_widgets = [ 420 self._back_button, 421 self._title_text, 422 self._status_text, 423 ] 424 425 # self._token_count_widget: bui.Widget | None = None 426 # self._smooth_update_timer: bui.AppTimer | None = None 427 # self._smooth_token_count: float | None = None 428 # self._token_count: int = 0 429 # self._smooth_increase_speed = 1.0 430 # self._ticking_sound: bui.Sound | None = None 431 432 # Get all textures used by our buttons preloading so hopefully 433 # they'll be in place by the time we show them. 434 for bdef in self._buttondefs: 435 for bimg in bdef.imgdefs: 436 bui.gettexture(bimg.tex) 437 438 self._state = self.State.LOADING 439 440 self._update_timer = bui.AppTimer( 441 0.789, bui.WeakCall(self._update), repeat=True 442 ) 443 self._update()
Inherited Members
- bauiv1._uitypes.Window
- get_root_widget
class
GetTokensWindow.State(enum.Enum):
61 class State(Enum): 62 """What are we doing?""" 63 64 LOADING = 'loading' 65 NOT_SIGNED_IN = 'not_signed_in' 66 HAVE_GOLD_PASS = 'have_gold_pass' 67 SHOWING_STORE = 'showing_store'
What are we doing?
Inherited Members
- enum.Enum
- name
- value
def
show_get_tokens_prompt() -> None:
835def show_get_tokens_prompt() -> None: 836 """Show a 'not enough tokens' prompt with an option to purchase more. 837 838 Note that the purchase option may not always be available 839 depending on the build of the game. 840 """ 841 from bauiv1lib.confirm import ConfirmWindow 842 843 assert bui.app.classic is not None 844 845 # Currently always allowing token purchases. 846 if bool(True): 847 ConfirmWindow( 848 bui.Lstr(resource='tokens.notEnoughTokensText'), 849 GetTokensWindow, 850 ok_text=bui.Lstr(resource='tokens.getTokensText'), 851 width=460, 852 height=130, 853 ) 854 else: 855 ConfirmWindow( 856 bui.Lstr(resource='tokens.notEnoughTokensText'), 857 cancel_button=False, 858 width=460, 859 height=130, 860 )
Show a 'not enough tokens' prompt with an option to purchase more.
Note that the purchase option may not always be available depending on the build of the game.