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):
42class TextContents(Enum):
43    """Some type of text to show."""
44
45    PRICE = 'price'

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?

LOADING = <State.LOADING: 'loading'>
NOT_SIGNED_IN = <State.NOT_SIGNED_IN: 'not_signed_in'>
HAVE_GOLD_PASS = <State.HAVE_GOLD_PASS: 'have_gold_pass'>
SHOWING_STORE = <State.SHOWING_STORE: 'showing_store'>
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.