bastd.ui.store.button

UI functionality for a button leading to the store.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""UI functionality for a button leading to the store."""
  4from __future__ import annotations
  5
  6from typing import TYPE_CHECKING
  7
  8import ba
  9import ba.internal
 10
 11if TYPE_CHECKING:
 12    from typing import Any, Sequence, Callable
 13
 14
 15class StoreButton:
 16    """A button leading to the store."""
 17
 18    def __init__(
 19        self,
 20        parent: ba.Widget,
 21        position: Sequence[float],
 22        size: Sequence[float],
 23        scale: float,
 24        on_activate_call: Callable[[], Any] | None = None,
 25        transition_delay: float | None = None,
 26        color: Sequence[float] | None = None,
 27        textcolor: Sequence[float] | None = None,
 28        show_tickets: bool = False,
 29        button_type: str | None = None,
 30        sale_scale: float = 1.0,
 31    ):
 32        self._position = position
 33        self._size = size
 34        self._scale = scale
 35
 36        if on_activate_call is None:
 37            on_activate_call = ba.WeakCall(self._default_on_activate_call)
 38        self._on_activate_call = on_activate_call
 39
 40        self._button = ba.buttonwidget(
 41            parent=parent,
 42            size=size,
 43            label='' if show_tickets else ba.Lstr(resource='storeText'),
 44            scale=scale,
 45            autoselect=True,
 46            on_activate_call=self._on_activate,
 47            transition_delay=transition_delay,
 48            color=color,
 49            button_type=button_type,
 50        )
 51
 52        self._title_text: ba.Widget | None
 53        self._ticket_text: ba.Widget | None
 54
 55        if show_tickets:
 56            self._title_text = ba.textwidget(
 57                parent=parent,
 58                position=(
 59                    position[0] + size[0] * 0.5 * scale,
 60                    position[1] + size[1] * 0.65 * scale,
 61                ),
 62                size=(0, 0),
 63                h_align='center',
 64                v_align='center',
 65                maxwidth=size[0] * scale * 0.65,
 66                text=ba.Lstr(resource='storeText'),
 67                draw_controller=self._button,
 68                scale=scale,
 69                transition_delay=transition_delay,
 70                color=textcolor,
 71            )
 72            self._ticket_text = ba.textwidget(
 73                parent=parent,
 74                size=(0, 0),
 75                h_align='center',
 76                v_align='center',
 77                maxwidth=size[0] * scale * 0.85,
 78                text='',
 79                color=(0.2, 1.0, 0.2),
 80                flatness=1.0,
 81                shadow=0.0,
 82                scale=scale * 0.6,
 83                transition_delay=transition_delay,
 84            )
 85        else:
 86            self._title_text = None
 87            self._ticket_text = None
 88
 89        self._circle_rad = 12 * scale
 90        self._circle_center = (0.0, 0.0)
 91        self._sale_circle_center = (0.0, 0.0)
 92
 93        self._available_purchase_backing = ba.imagewidget(
 94            parent=parent,
 95            color=(1, 0, 0),
 96            draw_controller=self._button,
 97            size=(2.2 * self._circle_rad, 2.2 * self._circle_rad),
 98            texture=ba.gettexture('circleShadow'),
 99            transition_delay=transition_delay,
100        )
101        self._available_purchase_text = ba.textwidget(
102            parent=parent,
103            size=(0, 0),
104            h_align='center',
105            v_align='center',
106            text='',
107            draw_controller=self._button,
108            color=(1, 1, 1),
109            flatness=1.0,
110            shadow=1.0,
111            scale=0.6 * scale,
112            maxwidth=self._circle_rad * 1.4,
113            transition_delay=transition_delay,
114        )
115
116        self._sale_circle_rad = 18 * scale * sale_scale
117        self._sale_backing = ba.imagewidget(
118            parent=parent,
119            color=(0.5, 0, 1.0),
120            draw_controller=self._button,
121            size=(2 * self._sale_circle_rad, 2 * self._sale_circle_rad),
122            texture=ba.gettexture('circleZigZag'),
123            transition_delay=transition_delay,
124        )
125        self._sale_title_text = ba.textwidget(
126            parent=parent,
127            size=(0, 0),
128            h_align='center',
129            v_align='center',
130            draw_controller=self._button,
131            color=(0, 1, 0),
132            flatness=1.0,
133            shadow=0.0,
134            scale=0.5 * scale * sale_scale,
135            maxwidth=self._sale_circle_rad * 1.5,
136            transition_delay=transition_delay,
137        )
138        self._sale_time_text = ba.textwidget(
139            parent=parent,
140            size=(0, 0),
141            h_align='center',
142            v_align='center',
143            draw_controller=self._button,
144            color=(0, 1, 0),
145            flatness=1.0,
146            shadow=0.0,
147            scale=0.4 * scale * sale_scale,
148            maxwidth=self._sale_circle_rad * 1.5,
149            transition_delay=transition_delay,
150        )
151
152        self.set_position(position)
153        self._update_timer = ba.Timer(
154            1.0,
155            ba.WeakCall(self._update),
156            repeat=True,
157            timetype=ba.TimeType.REAL,
158        )
159        self._update()
160
161    def _on_activate(self) -> None:
162        ba.internal.increment_analytics_count('Store button press')
163        self._on_activate_call()
164
165    def set_position(self, position: Sequence[float]) -> None:
166        """Set the button position."""
167        self._position = position
168        self._circle_center = (
169            position[0] + 0.1 * self._size[0] * self._scale,
170            position[1] + self._size[1] * self._scale * 0.8,
171        )
172        self._sale_circle_center = (
173            position[0] + 0.07 * self._size[0] * self._scale,
174            position[1] + self._size[1] * self._scale * 0.8,
175        )
176
177        if not self._button:
178            return
179        ba.buttonwidget(edit=self._button, position=self._position)
180        if self._title_text is not None:
181            ba.textwidget(
182                edit=self._title_text,
183                position=(
184                    self._position[0] + self._size[0] * 0.5 * self._scale,
185                    self._position[1] + self._size[1] * 0.65 * self._scale,
186                ),
187            )
188        if self._ticket_text is not None:
189            ba.textwidget(
190                edit=self._ticket_text,
191                position=(
192                    position[0] + self._size[0] * 0.5 * self._scale,
193                    position[1] + self._size[1] * 0.28 * self._scale,
194                ),
195                size=(0, 0),
196            )
197        ba.imagewidget(
198            edit=self._available_purchase_backing,
199            position=(
200                self._circle_center[0] - self._circle_rad * 1.02,
201                self._circle_center[1] - self._circle_rad * 1.13,
202            ),
203        )
204        ba.textwidget(
205            edit=self._available_purchase_text, position=self._circle_center
206        )
207
208        ba.imagewidget(
209            edit=self._sale_backing,
210            position=(
211                self._sale_circle_center[0] - self._sale_circle_rad,
212                self._sale_circle_center[1] - self._sale_circle_rad,
213            ),
214        )
215        ba.textwidget(
216            edit=self._sale_title_text,
217            position=(
218                self._sale_circle_center[0],
219                self._sale_circle_center[1] + self._sale_circle_rad * 0.3,
220            ),
221        )
222        ba.textwidget(
223            edit=self._sale_time_text,
224            position=(
225                self._sale_circle_center[0],
226                self._sale_circle_center[1] - self._sale_circle_rad * 0.3,
227            ),
228        )
229
230    def _default_on_activate_call(self) -> None:
231        # pylint: disable=cyclic-import
232        from bastd.ui.account import show_sign_in_prompt
233        from bastd.ui.store.browser import StoreBrowserWindow
234
235        if ba.internal.get_v1_account_state() != 'signed_in':
236            show_sign_in_prompt()
237            return
238        StoreBrowserWindow(modal=True, origin_widget=self._button)
239
240    def get_button(self) -> ba.Widget:
241        """Return the underlying button widget."""
242        return self._button
243
244    def _update(self) -> None:
245        # pylint: disable=too-many-branches
246        # pylint: disable=cyclic-import
247        from ba import SpecialChar, TimeFormat
248        from ba.internal import (
249            get_available_sale_time,
250            get_available_purchase_count,
251        )
252
253        if not self._button:
254            return  # Our instance may outlive our UI objects.
255
256        if self._ticket_text is not None:
257            if ba.internal.get_v1_account_state() == 'signed_in':
258                sval = ba.charstr(SpecialChar.TICKET) + str(
259                    ba.internal.get_v1_account_ticket_count()
260                )
261            else:
262                sval = '-'
263            ba.textwidget(edit=self._ticket_text, text=sval)
264        available_purchases = get_available_purchase_count()
265
266        # Old pro sale stuff..
267        sale_time = get_available_sale_time('extras')
268
269        # ..also look for new style sales.
270        if sale_time is None:
271            import datetime
272
273            sales_raw = ba.internal.get_v1_account_misc_read_val('sales', {})
274            sale_times = []
275            try:
276                # Look at the current set of sales; filter any with time
277                # remaining that we don't own.
278                for sale_item, sale_info in list(sales_raw.items()):
279                    if not ba.internal.get_purchased(sale_item):
280                        to_end = (
281                            datetime.datetime.utcfromtimestamp(sale_info['e'])
282                            - datetime.datetime.utcnow()
283                        ).total_seconds()
284                        if to_end > 0:
285                            sale_times.append(to_end)
286            except Exception:
287                ba.print_exception('Error parsing sales.')
288            if sale_times:
289                sale_time = int(min(sale_times) * 1000)
290
291        if sale_time is not None:
292            ba.textwidget(
293                edit=self._sale_title_text,
294                text=ba.Lstr(resource='store.saleText'),
295            )
296            ba.textwidget(
297                edit=self._sale_time_text,
298                text=ba.timestring(
299                    sale_time, centi=False, timeformat=TimeFormat.MILLISECONDS
300                ),
301            )
302            ba.imagewidget(edit=self._sale_backing, opacity=1.0)
303            ba.imagewidget(edit=self._available_purchase_backing, opacity=1.0)
304            ba.textwidget(edit=self._available_purchase_text, text='')
305            ba.imagewidget(edit=self._available_purchase_backing, opacity=0.0)
306        else:
307            ba.imagewidget(edit=self._sale_backing, opacity=0.0)
308            ba.textwidget(edit=self._sale_time_text, text='')
309            ba.textwidget(edit=self._sale_title_text, text='')
310            if available_purchases > 0:
311                ba.textwidget(
312                    edit=self._available_purchase_text,
313                    text=str(available_purchases),
314                )
315                ba.imagewidget(
316                    edit=self._available_purchase_backing, opacity=1.0
317                )
318            else:
319                ba.textwidget(edit=self._available_purchase_text, text='')
320                ba.imagewidget(
321                    edit=self._available_purchase_backing, opacity=0.0
322                )
class StoreButton:
 16class StoreButton:
 17    """A button leading to the store."""
 18
 19    def __init__(
 20        self,
 21        parent: ba.Widget,
 22        position: Sequence[float],
 23        size: Sequence[float],
 24        scale: float,
 25        on_activate_call: Callable[[], Any] | None = None,
 26        transition_delay: float | None = None,
 27        color: Sequence[float] | None = None,
 28        textcolor: Sequence[float] | None = None,
 29        show_tickets: bool = False,
 30        button_type: str | None = None,
 31        sale_scale: float = 1.0,
 32    ):
 33        self._position = position
 34        self._size = size
 35        self._scale = scale
 36
 37        if on_activate_call is None:
 38            on_activate_call = ba.WeakCall(self._default_on_activate_call)
 39        self._on_activate_call = on_activate_call
 40
 41        self._button = ba.buttonwidget(
 42            parent=parent,
 43            size=size,
 44            label='' if show_tickets else ba.Lstr(resource='storeText'),
 45            scale=scale,
 46            autoselect=True,
 47            on_activate_call=self._on_activate,
 48            transition_delay=transition_delay,
 49            color=color,
 50            button_type=button_type,
 51        )
 52
 53        self._title_text: ba.Widget | None
 54        self._ticket_text: ba.Widget | None
 55
 56        if show_tickets:
 57            self._title_text = ba.textwidget(
 58                parent=parent,
 59                position=(
 60                    position[0] + size[0] * 0.5 * scale,
 61                    position[1] + size[1] * 0.65 * scale,
 62                ),
 63                size=(0, 0),
 64                h_align='center',
 65                v_align='center',
 66                maxwidth=size[0] * scale * 0.65,
 67                text=ba.Lstr(resource='storeText'),
 68                draw_controller=self._button,
 69                scale=scale,
 70                transition_delay=transition_delay,
 71                color=textcolor,
 72            )
 73            self._ticket_text = ba.textwidget(
 74                parent=parent,
 75                size=(0, 0),
 76                h_align='center',
 77                v_align='center',
 78                maxwidth=size[0] * scale * 0.85,
 79                text='',
 80                color=(0.2, 1.0, 0.2),
 81                flatness=1.0,
 82                shadow=0.0,
 83                scale=scale * 0.6,
 84                transition_delay=transition_delay,
 85            )
 86        else:
 87            self._title_text = None
 88            self._ticket_text = None
 89
 90        self._circle_rad = 12 * scale
 91        self._circle_center = (0.0, 0.0)
 92        self._sale_circle_center = (0.0, 0.0)
 93
 94        self._available_purchase_backing = ba.imagewidget(
 95            parent=parent,
 96            color=(1, 0, 0),
 97            draw_controller=self._button,
 98            size=(2.2 * self._circle_rad, 2.2 * self._circle_rad),
 99            texture=ba.gettexture('circleShadow'),
100            transition_delay=transition_delay,
101        )
102        self._available_purchase_text = ba.textwidget(
103            parent=parent,
104            size=(0, 0),
105            h_align='center',
106            v_align='center',
107            text='',
108            draw_controller=self._button,
109            color=(1, 1, 1),
110            flatness=1.0,
111            shadow=1.0,
112            scale=0.6 * scale,
113            maxwidth=self._circle_rad * 1.4,
114            transition_delay=transition_delay,
115        )
116
117        self._sale_circle_rad = 18 * scale * sale_scale
118        self._sale_backing = ba.imagewidget(
119            parent=parent,
120            color=(0.5, 0, 1.0),
121            draw_controller=self._button,
122            size=(2 * self._sale_circle_rad, 2 * self._sale_circle_rad),
123            texture=ba.gettexture('circleZigZag'),
124            transition_delay=transition_delay,
125        )
126        self._sale_title_text = ba.textwidget(
127            parent=parent,
128            size=(0, 0),
129            h_align='center',
130            v_align='center',
131            draw_controller=self._button,
132            color=(0, 1, 0),
133            flatness=1.0,
134            shadow=0.0,
135            scale=0.5 * scale * sale_scale,
136            maxwidth=self._sale_circle_rad * 1.5,
137            transition_delay=transition_delay,
138        )
139        self._sale_time_text = ba.textwidget(
140            parent=parent,
141            size=(0, 0),
142            h_align='center',
143            v_align='center',
144            draw_controller=self._button,
145            color=(0, 1, 0),
146            flatness=1.0,
147            shadow=0.0,
148            scale=0.4 * scale * sale_scale,
149            maxwidth=self._sale_circle_rad * 1.5,
150            transition_delay=transition_delay,
151        )
152
153        self.set_position(position)
154        self._update_timer = ba.Timer(
155            1.0,
156            ba.WeakCall(self._update),
157            repeat=True,
158            timetype=ba.TimeType.REAL,
159        )
160        self._update()
161
162    def _on_activate(self) -> None:
163        ba.internal.increment_analytics_count('Store button press')
164        self._on_activate_call()
165
166    def set_position(self, position: Sequence[float]) -> None:
167        """Set the button position."""
168        self._position = position
169        self._circle_center = (
170            position[0] + 0.1 * self._size[0] * self._scale,
171            position[1] + self._size[1] * self._scale * 0.8,
172        )
173        self._sale_circle_center = (
174            position[0] + 0.07 * self._size[0] * self._scale,
175            position[1] + self._size[1] * self._scale * 0.8,
176        )
177
178        if not self._button:
179            return
180        ba.buttonwidget(edit=self._button, position=self._position)
181        if self._title_text is not None:
182            ba.textwidget(
183                edit=self._title_text,
184                position=(
185                    self._position[0] + self._size[0] * 0.5 * self._scale,
186                    self._position[1] + self._size[1] * 0.65 * self._scale,
187                ),
188            )
189        if self._ticket_text is not None:
190            ba.textwidget(
191                edit=self._ticket_text,
192                position=(
193                    position[0] + self._size[0] * 0.5 * self._scale,
194                    position[1] + self._size[1] * 0.28 * self._scale,
195                ),
196                size=(0, 0),
197            )
198        ba.imagewidget(
199            edit=self._available_purchase_backing,
200            position=(
201                self._circle_center[0] - self._circle_rad * 1.02,
202                self._circle_center[1] - self._circle_rad * 1.13,
203            ),
204        )
205        ba.textwidget(
206            edit=self._available_purchase_text, position=self._circle_center
207        )
208
209        ba.imagewidget(
210            edit=self._sale_backing,
211            position=(
212                self._sale_circle_center[0] - self._sale_circle_rad,
213                self._sale_circle_center[1] - self._sale_circle_rad,
214            ),
215        )
216        ba.textwidget(
217            edit=self._sale_title_text,
218            position=(
219                self._sale_circle_center[0],
220                self._sale_circle_center[1] + self._sale_circle_rad * 0.3,
221            ),
222        )
223        ba.textwidget(
224            edit=self._sale_time_text,
225            position=(
226                self._sale_circle_center[0],
227                self._sale_circle_center[1] - self._sale_circle_rad * 0.3,
228            ),
229        )
230
231    def _default_on_activate_call(self) -> None:
232        # pylint: disable=cyclic-import
233        from bastd.ui.account import show_sign_in_prompt
234        from bastd.ui.store.browser import StoreBrowserWindow
235
236        if ba.internal.get_v1_account_state() != 'signed_in':
237            show_sign_in_prompt()
238            return
239        StoreBrowserWindow(modal=True, origin_widget=self._button)
240
241    def get_button(self) -> ba.Widget:
242        """Return the underlying button widget."""
243        return self._button
244
245    def _update(self) -> None:
246        # pylint: disable=too-many-branches
247        # pylint: disable=cyclic-import
248        from ba import SpecialChar, TimeFormat
249        from ba.internal import (
250            get_available_sale_time,
251            get_available_purchase_count,
252        )
253
254        if not self._button:
255            return  # Our instance may outlive our UI objects.
256
257        if self._ticket_text is not None:
258            if ba.internal.get_v1_account_state() == 'signed_in':
259                sval = ba.charstr(SpecialChar.TICKET) + str(
260                    ba.internal.get_v1_account_ticket_count()
261                )
262            else:
263                sval = '-'
264            ba.textwidget(edit=self._ticket_text, text=sval)
265        available_purchases = get_available_purchase_count()
266
267        # Old pro sale stuff..
268        sale_time = get_available_sale_time('extras')
269
270        # ..also look for new style sales.
271        if sale_time is None:
272            import datetime
273
274            sales_raw = ba.internal.get_v1_account_misc_read_val('sales', {})
275            sale_times = []
276            try:
277                # Look at the current set of sales; filter any with time
278                # remaining that we don't own.
279                for sale_item, sale_info in list(sales_raw.items()):
280                    if not ba.internal.get_purchased(sale_item):
281                        to_end = (
282                            datetime.datetime.utcfromtimestamp(sale_info['e'])
283                            - datetime.datetime.utcnow()
284                        ).total_seconds()
285                        if to_end > 0:
286                            sale_times.append(to_end)
287            except Exception:
288                ba.print_exception('Error parsing sales.')
289            if sale_times:
290                sale_time = int(min(sale_times) * 1000)
291
292        if sale_time is not None:
293            ba.textwidget(
294                edit=self._sale_title_text,
295                text=ba.Lstr(resource='store.saleText'),
296            )
297            ba.textwidget(
298                edit=self._sale_time_text,
299                text=ba.timestring(
300                    sale_time, centi=False, timeformat=TimeFormat.MILLISECONDS
301                ),
302            )
303            ba.imagewidget(edit=self._sale_backing, opacity=1.0)
304            ba.imagewidget(edit=self._available_purchase_backing, opacity=1.0)
305            ba.textwidget(edit=self._available_purchase_text, text='')
306            ba.imagewidget(edit=self._available_purchase_backing, opacity=0.0)
307        else:
308            ba.imagewidget(edit=self._sale_backing, opacity=0.0)
309            ba.textwidget(edit=self._sale_time_text, text='')
310            ba.textwidget(edit=self._sale_title_text, text='')
311            if available_purchases > 0:
312                ba.textwidget(
313                    edit=self._available_purchase_text,
314                    text=str(available_purchases),
315                )
316                ba.imagewidget(
317                    edit=self._available_purchase_backing, opacity=1.0
318                )
319            else:
320                ba.textwidget(edit=self._available_purchase_text, text='')
321                ba.imagewidget(
322                    edit=self._available_purchase_backing, opacity=0.0
323                )

A button leading to the store.

StoreButton( parent: _ba.Widget, position: Sequence[float], size: Sequence[float], scale: float, on_activate_call: Optional[Callable[[], Any]] = None, transition_delay: float | None = None, color: Optional[Sequence[float]] = None, textcolor: Optional[Sequence[float]] = None, show_tickets: bool = False, button_type: str | None = None, sale_scale: float = 1.0)
 19    def __init__(
 20        self,
 21        parent: ba.Widget,
 22        position: Sequence[float],
 23        size: Sequence[float],
 24        scale: float,
 25        on_activate_call: Callable[[], Any] | None = None,
 26        transition_delay: float | None = None,
 27        color: Sequence[float] | None = None,
 28        textcolor: Sequence[float] | None = None,
 29        show_tickets: bool = False,
 30        button_type: str | None = None,
 31        sale_scale: float = 1.0,
 32    ):
 33        self._position = position
 34        self._size = size
 35        self._scale = scale
 36
 37        if on_activate_call is None:
 38            on_activate_call = ba.WeakCall(self._default_on_activate_call)
 39        self._on_activate_call = on_activate_call
 40
 41        self._button = ba.buttonwidget(
 42            parent=parent,
 43            size=size,
 44            label='' if show_tickets else ba.Lstr(resource='storeText'),
 45            scale=scale,
 46            autoselect=True,
 47            on_activate_call=self._on_activate,
 48            transition_delay=transition_delay,
 49            color=color,
 50            button_type=button_type,
 51        )
 52
 53        self._title_text: ba.Widget | None
 54        self._ticket_text: ba.Widget | None
 55
 56        if show_tickets:
 57            self._title_text = ba.textwidget(
 58                parent=parent,
 59                position=(
 60                    position[0] + size[0] * 0.5 * scale,
 61                    position[1] + size[1] * 0.65 * scale,
 62                ),
 63                size=(0, 0),
 64                h_align='center',
 65                v_align='center',
 66                maxwidth=size[0] * scale * 0.65,
 67                text=ba.Lstr(resource='storeText'),
 68                draw_controller=self._button,
 69                scale=scale,
 70                transition_delay=transition_delay,
 71                color=textcolor,
 72            )
 73            self._ticket_text = ba.textwidget(
 74                parent=parent,
 75                size=(0, 0),
 76                h_align='center',
 77                v_align='center',
 78                maxwidth=size[0] * scale * 0.85,
 79                text='',
 80                color=(0.2, 1.0, 0.2),
 81                flatness=1.0,
 82                shadow=0.0,
 83                scale=scale * 0.6,
 84                transition_delay=transition_delay,
 85            )
 86        else:
 87            self._title_text = None
 88            self._ticket_text = None
 89
 90        self._circle_rad = 12 * scale
 91        self._circle_center = (0.0, 0.0)
 92        self._sale_circle_center = (0.0, 0.0)
 93
 94        self._available_purchase_backing = ba.imagewidget(
 95            parent=parent,
 96            color=(1, 0, 0),
 97            draw_controller=self._button,
 98            size=(2.2 * self._circle_rad, 2.2 * self._circle_rad),
 99            texture=ba.gettexture('circleShadow'),
100            transition_delay=transition_delay,
101        )
102        self._available_purchase_text = ba.textwidget(
103            parent=parent,
104            size=(0, 0),
105            h_align='center',
106            v_align='center',
107            text='',
108            draw_controller=self._button,
109            color=(1, 1, 1),
110            flatness=1.0,
111            shadow=1.0,
112            scale=0.6 * scale,
113            maxwidth=self._circle_rad * 1.4,
114            transition_delay=transition_delay,
115        )
116
117        self._sale_circle_rad = 18 * scale * sale_scale
118        self._sale_backing = ba.imagewidget(
119            parent=parent,
120            color=(0.5, 0, 1.0),
121            draw_controller=self._button,
122            size=(2 * self._sale_circle_rad, 2 * self._sale_circle_rad),
123            texture=ba.gettexture('circleZigZag'),
124            transition_delay=transition_delay,
125        )
126        self._sale_title_text = ba.textwidget(
127            parent=parent,
128            size=(0, 0),
129            h_align='center',
130            v_align='center',
131            draw_controller=self._button,
132            color=(0, 1, 0),
133            flatness=1.0,
134            shadow=0.0,
135            scale=0.5 * scale * sale_scale,
136            maxwidth=self._sale_circle_rad * 1.5,
137            transition_delay=transition_delay,
138        )
139        self._sale_time_text = ba.textwidget(
140            parent=parent,
141            size=(0, 0),
142            h_align='center',
143            v_align='center',
144            draw_controller=self._button,
145            color=(0, 1, 0),
146            flatness=1.0,
147            shadow=0.0,
148            scale=0.4 * scale * sale_scale,
149            maxwidth=self._sale_circle_rad * 1.5,
150            transition_delay=transition_delay,
151        )
152
153        self.set_position(position)
154        self._update_timer = ba.Timer(
155            1.0,
156            ba.WeakCall(self._update),
157            repeat=True,
158            timetype=ba.TimeType.REAL,
159        )
160        self._update()
def set_position(self, position: Sequence[float]) -> None:
166    def set_position(self, position: Sequence[float]) -> None:
167        """Set the button position."""
168        self._position = position
169        self._circle_center = (
170            position[0] + 0.1 * self._size[0] * self._scale,
171            position[1] + self._size[1] * self._scale * 0.8,
172        )
173        self._sale_circle_center = (
174            position[0] + 0.07 * self._size[0] * self._scale,
175            position[1] + self._size[1] * self._scale * 0.8,
176        )
177
178        if not self._button:
179            return
180        ba.buttonwidget(edit=self._button, position=self._position)
181        if self._title_text is not None:
182            ba.textwidget(
183                edit=self._title_text,
184                position=(
185                    self._position[0] + self._size[0] * 0.5 * self._scale,
186                    self._position[1] + self._size[1] * 0.65 * self._scale,
187                ),
188            )
189        if self._ticket_text is not None:
190            ba.textwidget(
191                edit=self._ticket_text,
192                position=(
193                    position[0] + self._size[0] * 0.5 * self._scale,
194                    position[1] + self._size[1] * 0.28 * self._scale,
195                ),
196                size=(0, 0),
197            )
198        ba.imagewidget(
199            edit=self._available_purchase_backing,
200            position=(
201                self._circle_center[0] - self._circle_rad * 1.02,
202                self._circle_center[1] - self._circle_rad * 1.13,
203            ),
204        )
205        ba.textwidget(
206            edit=self._available_purchase_text, position=self._circle_center
207        )
208
209        ba.imagewidget(
210            edit=self._sale_backing,
211            position=(
212                self._sale_circle_center[0] - self._sale_circle_rad,
213                self._sale_circle_center[1] - self._sale_circle_rad,
214            ),
215        )
216        ba.textwidget(
217            edit=self._sale_title_text,
218            position=(
219                self._sale_circle_center[0],
220                self._sale_circle_center[1] + self._sale_circle_rad * 0.3,
221            ),
222        )
223        ba.textwidget(
224            edit=self._sale_time_text,
225            position=(
226                self._sale_circle_center[0],
227                self._sale_circle_center[1] - self._sale_circle_rad * 0.3,
228            ),
229        )

Set the button position.

def get_button(self) -> _ba.Widget:
241    def get_button(self) -> ba.Widget:
242        """Return the underlying button widget."""
243        return self._button

Return the underlying button widget.