bauiv1lib.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
  6import logging
  7from typing import TYPE_CHECKING
  8
  9import bauiv1 as bui
 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: bui.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 = bui.WeakCall(self._default_on_activate_call)
 38        self._on_activate_call = on_activate_call
 39
 40        self._button = bui.buttonwidget(
 41            parent=parent,
 42            size=size,
 43            label='' if show_tickets else bui.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: bui.Widget | None
 53        self._ticket_text: bui.Widget | None
 54
 55        if show_tickets:
 56            self._title_text = bui.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=bui.Lstr(resource='storeText'),
 67                draw_controller=self._button,
 68                scale=scale,
 69                transition_delay=transition_delay,
 70                color=textcolor,
 71            )
 72            self._ticket_text = bui.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 = bui.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=bui.gettexture('circleShadow'),
 99            transition_delay=transition_delay,
100        )
101        self._available_purchase_text = bui.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 = bui.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=bui.gettexture('circleZigZag'),
123            transition_delay=transition_delay,
124        )
125        self._sale_title_text = bui.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 = bui.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 = bui.AppTimer(
154            1.0, bui.WeakCall(self._update), repeat=True
155        )
156        self._update()
157
158    def _on_activate(self) -> None:
159        bui.increment_analytics_count('Store button press')
160        self._on_activate_call()
161
162    def set_position(self, position: Sequence[float]) -> None:
163        """Set the button position."""
164        self._position = position
165        self._circle_center = (
166            position[0] + 0.1 * self._size[0] * self._scale,
167            position[1] + self._size[1] * self._scale * 0.8,
168        )
169        self._sale_circle_center = (
170            position[0] + 0.07 * self._size[0] * self._scale,
171            position[1] + self._size[1] * self._scale * 0.8,
172        )
173
174        if not self._button:
175            return
176        bui.buttonwidget(edit=self._button, position=self._position)
177        if self._title_text is not None:
178            bui.textwidget(
179                edit=self._title_text,
180                position=(
181                    self._position[0] + self._size[0] * 0.5 * self._scale,
182                    self._position[1] + self._size[1] * 0.65 * self._scale,
183                ),
184            )
185        if self._ticket_text is not None:
186            bui.textwidget(
187                edit=self._ticket_text,
188                position=(
189                    position[0] + self._size[0] * 0.5 * self._scale,
190                    position[1] + self._size[1] * 0.28 * self._scale,
191                ),
192                size=(0, 0),
193            )
194        bui.imagewidget(
195            edit=self._available_purchase_backing,
196            position=(
197                self._circle_center[0] - self._circle_rad * 1.02,
198                self._circle_center[1] - self._circle_rad * 1.13,
199            ),
200        )
201        bui.textwidget(
202            edit=self._available_purchase_text, position=self._circle_center
203        )
204
205        bui.imagewidget(
206            edit=self._sale_backing,
207            position=(
208                self._sale_circle_center[0] - self._sale_circle_rad,
209                self._sale_circle_center[1] - self._sale_circle_rad,
210            ),
211        )
212        bui.textwidget(
213            edit=self._sale_title_text,
214            position=(
215                self._sale_circle_center[0],
216                self._sale_circle_center[1] + self._sale_circle_rad * 0.3,
217            ),
218        )
219        bui.textwidget(
220            edit=self._sale_time_text,
221            position=(
222                self._sale_circle_center[0],
223                self._sale_circle_center[1] - self._sale_circle_rad * 0.3,
224            ),
225        )
226
227    def _default_on_activate_call(self) -> None:
228        # pylint: disable=cyclic-import
229        from bauiv1lib.account import show_sign_in_prompt
230        from bauiv1lib.store.browser import StoreBrowserWindow
231
232        plus = bui.app.plus
233        assert plus is not None
234        if plus.get_v1_account_state() != 'signed_in':
235            show_sign_in_prompt()
236            return
237        StoreBrowserWindow(modal=True, origin_widget=self._button)
238
239    def get_button(self) -> bui.Widget:
240        """Return the underlying button widget."""
241        return self._button
242
243    def _update(self) -> None:
244        # pylint: disable=too-many-branches
245        # pylint: disable=cyclic-import
246        from babase import SpecialChar
247
248        plus = bui.app.plus
249        assert plus is not None
250        assert bui.app.classic is not None
251        store = bui.app.classic.store
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 plus.get_v1_account_state() == 'signed_in':
258                sval = bui.charstr(SpecialChar.TICKET) + str(
259                    plus.get_v1_account_ticket_count()
260                )
261            else:
262                sval = '-'
263            bui.textwidget(edit=self._ticket_text, text=sval)
264        available_purchases = store.get_available_purchase_count()
265
266        # Old pro sale stuff..
267        sale_time = store.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 = plus.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 plus.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                logging.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            bui.textwidget(
293                edit=self._sale_title_text,
294                text=bui.Lstr(resource='store.saleText'),
295            )
296            bui.textwidget(
297                edit=self._sale_time_text,
298                text=bui.timestring(sale_time / 1000.0, centi=False),
299            )
300            bui.imagewidget(edit=self._sale_backing, opacity=1.0)
301            bui.imagewidget(edit=self._available_purchase_backing, opacity=1.0)
302            bui.textwidget(edit=self._available_purchase_text, text='')
303            bui.imagewidget(edit=self._available_purchase_backing, opacity=0.0)
304        else:
305            bui.imagewidget(edit=self._sale_backing, opacity=0.0)
306            bui.textwidget(edit=self._sale_time_text, text='')
307            bui.textwidget(edit=self._sale_title_text, text='')
308            if available_purchases > 0:
309                bui.textwidget(
310                    edit=self._available_purchase_text,
311                    text=str(available_purchases),
312                )
313                bui.imagewidget(
314                    edit=self._available_purchase_backing, opacity=1.0
315                )
316            else:
317                bui.textwidget(edit=self._available_purchase_text, text='')
318                bui.imagewidget(
319                    edit=self._available_purchase_backing, opacity=0.0
320                )
class StoreButton:
 16class StoreButton:
 17    """A button leading to the store."""
 18
 19    def __init__(
 20        self,
 21        parent: bui.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 = bui.WeakCall(self._default_on_activate_call)
 39        self._on_activate_call = on_activate_call
 40
 41        self._button = bui.buttonwidget(
 42            parent=parent,
 43            size=size,
 44            label='' if show_tickets else bui.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: bui.Widget | None
 54        self._ticket_text: bui.Widget | None
 55
 56        if show_tickets:
 57            self._title_text = bui.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=bui.Lstr(resource='storeText'),
 68                draw_controller=self._button,
 69                scale=scale,
 70                transition_delay=transition_delay,
 71                color=textcolor,
 72            )
 73            self._ticket_text = bui.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 = bui.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=bui.gettexture('circleShadow'),
100            transition_delay=transition_delay,
101        )
102        self._available_purchase_text = bui.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 = bui.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=bui.gettexture('circleZigZag'),
124            transition_delay=transition_delay,
125        )
126        self._sale_title_text = bui.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 = bui.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 = bui.AppTimer(
155            1.0, bui.WeakCall(self._update), repeat=True
156        )
157        self._update()
158
159    def _on_activate(self) -> None:
160        bui.increment_analytics_count('Store button press')
161        self._on_activate_call()
162
163    def set_position(self, position: Sequence[float]) -> None:
164        """Set the button position."""
165        self._position = position
166        self._circle_center = (
167            position[0] + 0.1 * self._size[0] * self._scale,
168            position[1] + self._size[1] * self._scale * 0.8,
169        )
170        self._sale_circle_center = (
171            position[0] + 0.07 * self._size[0] * self._scale,
172            position[1] + self._size[1] * self._scale * 0.8,
173        )
174
175        if not self._button:
176            return
177        bui.buttonwidget(edit=self._button, position=self._position)
178        if self._title_text is not None:
179            bui.textwidget(
180                edit=self._title_text,
181                position=(
182                    self._position[0] + self._size[0] * 0.5 * self._scale,
183                    self._position[1] + self._size[1] * 0.65 * self._scale,
184                ),
185            )
186        if self._ticket_text is not None:
187            bui.textwidget(
188                edit=self._ticket_text,
189                position=(
190                    position[0] + self._size[0] * 0.5 * self._scale,
191                    position[1] + self._size[1] * 0.28 * self._scale,
192                ),
193                size=(0, 0),
194            )
195        bui.imagewidget(
196            edit=self._available_purchase_backing,
197            position=(
198                self._circle_center[0] - self._circle_rad * 1.02,
199                self._circle_center[1] - self._circle_rad * 1.13,
200            ),
201        )
202        bui.textwidget(
203            edit=self._available_purchase_text, position=self._circle_center
204        )
205
206        bui.imagewidget(
207            edit=self._sale_backing,
208            position=(
209                self._sale_circle_center[0] - self._sale_circle_rad,
210                self._sale_circle_center[1] - self._sale_circle_rad,
211            ),
212        )
213        bui.textwidget(
214            edit=self._sale_title_text,
215            position=(
216                self._sale_circle_center[0],
217                self._sale_circle_center[1] + self._sale_circle_rad * 0.3,
218            ),
219        )
220        bui.textwidget(
221            edit=self._sale_time_text,
222            position=(
223                self._sale_circle_center[0],
224                self._sale_circle_center[1] - self._sale_circle_rad * 0.3,
225            ),
226        )
227
228    def _default_on_activate_call(self) -> None:
229        # pylint: disable=cyclic-import
230        from bauiv1lib.account import show_sign_in_prompt
231        from bauiv1lib.store.browser import StoreBrowserWindow
232
233        plus = bui.app.plus
234        assert plus is not None
235        if plus.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) -> bui.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 babase import SpecialChar
248
249        plus = bui.app.plus
250        assert plus is not None
251        assert bui.app.classic is not None
252        store = bui.app.classic.store
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 plus.get_v1_account_state() == 'signed_in':
259                sval = bui.charstr(SpecialChar.TICKET) + str(
260                    plus.get_v1_account_ticket_count()
261                )
262            else:
263                sval = '-'
264            bui.textwidget(edit=self._ticket_text, text=sval)
265        available_purchases = store.get_available_purchase_count()
266
267        # Old pro sale stuff..
268        sale_time = store.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 = plus.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 plus.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                logging.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            bui.textwidget(
294                edit=self._sale_title_text,
295                text=bui.Lstr(resource='store.saleText'),
296            )
297            bui.textwidget(
298                edit=self._sale_time_text,
299                text=bui.timestring(sale_time / 1000.0, centi=False),
300            )
301            bui.imagewidget(edit=self._sale_backing, opacity=1.0)
302            bui.imagewidget(edit=self._available_purchase_backing, opacity=1.0)
303            bui.textwidget(edit=self._available_purchase_text, text='')
304            bui.imagewidget(edit=self._available_purchase_backing, opacity=0.0)
305        else:
306            bui.imagewidget(edit=self._sale_backing, opacity=0.0)
307            bui.textwidget(edit=self._sale_time_text, text='')
308            bui.textwidget(edit=self._sale_title_text, text='')
309            if available_purchases > 0:
310                bui.textwidget(
311                    edit=self._available_purchase_text,
312                    text=str(available_purchases),
313                )
314                bui.imagewidget(
315                    edit=self._available_purchase_backing, opacity=1.0
316                )
317            else:
318                bui.textwidget(edit=self._available_purchase_text, text='')
319                bui.imagewidget(
320                    edit=self._available_purchase_backing, opacity=0.0
321                )

A button leading to the store.

StoreButton( parent: _bauiv1.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: bui.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 = bui.WeakCall(self._default_on_activate_call)
 39        self._on_activate_call = on_activate_call
 40
 41        self._button = bui.buttonwidget(
 42            parent=parent,
 43            size=size,
 44            label='' if show_tickets else bui.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: bui.Widget | None
 54        self._ticket_text: bui.Widget | None
 55
 56        if show_tickets:
 57            self._title_text = bui.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=bui.Lstr(resource='storeText'),
 68                draw_controller=self._button,
 69                scale=scale,
 70                transition_delay=transition_delay,
 71                color=textcolor,
 72            )
 73            self._ticket_text = bui.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 = bui.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=bui.gettexture('circleShadow'),
100            transition_delay=transition_delay,
101        )
102        self._available_purchase_text = bui.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 = bui.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=bui.gettexture('circleZigZag'),
124            transition_delay=transition_delay,
125        )
126        self._sale_title_text = bui.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 = bui.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 = bui.AppTimer(
155            1.0, bui.WeakCall(self._update), repeat=True
156        )
157        self._update()
def set_position(self, position: Sequence[float]) -> None:
163    def set_position(self, position: Sequence[float]) -> None:
164        """Set the button position."""
165        self._position = position
166        self._circle_center = (
167            position[0] + 0.1 * self._size[0] * self._scale,
168            position[1] + self._size[1] * self._scale * 0.8,
169        )
170        self._sale_circle_center = (
171            position[0] + 0.07 * self._size[0] * self._scale,
172            position[1] + self._size[1] * self._scale * 0.8,
173        )
174
175        if not self._button:
176            return
177        bui.buttonwidget(edit=self._button, position=self._position)
178        if self._title_text is not None:
179            bui.textwidget(
180                edit=self._title_text,
181                position=(
182                    self._position[0] + self._size[0] * 0.5 * self._scale,
183                    self._position[1] + self._size[1] * 0.65 * self._scale,
184                ),
185            )
186        if self._ticket_text is not None:
187            bui.textwidget(
188                edit=self._ticket_text,
189                position=(
190                    position[0] + self._size[0] * 0.5 * self._scale,
191                    position[1] + self._size[1] * 0.28 * self._scale,
192                ),
193                size=(0, 0),
194            )
195        bui.imagewidget(
196            edit=self._available_purchase_backing,
197            position=(
198                self._circle_center[0] - self._circle_rad * 1.02,
199                self._circle_center[1] - self._circle_rad * 1.13,
200            ),
201        )
202        bui.textwidget(
203            edit=self._available_purchase_text, position=self._circle_center
204        )
205
206        bui.imagewidget(
207            edit=self._sale_backing,
208            position=(
209                self._sale_circle_center[0] - self._sale_circle_rad,
210                self._sale_circle_center[1] - self._sale_circle_rad,
211            ),
212        )
213        bui.textwidget(
214            edit=self._sale_title_text,
215            position=(
216                self._sale_circle_center[0],
217                self._sale_circle_center[1] + self._sale_circle_rad * 0.3,
218            ),
219        )
220        bui.textwidget(
221            edit=self._sale_time_text,
222            position=(
223                self._sale_circle_center[0],
224                self._sale_circle_center[1] - self._sale_circle_rad * 0.3,
225            ),
226        )

Set the button position.

def get_button(self) -> _bauiv1.Widget:
240    def get_button(self) -> bui.Widget:
241        """Return the underlying button widget."""
242        return self._button

Return the underlying button widget.