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