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

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)
 21    def __init__(
 22        self,
 23        parent: bui.Widget,
 24        position: Sequence[float],
 25        size: Sequence[float],
 26        scale: float,
 27        on_activate_call: Callable[[], Any] | None = None,
 28        transition_delay: float | None = None,
 29        color: Sequence[float] | None = None,
 30        textcolor: Sequence[float] | None = None,
 31        show_tickets: bool = False,
 32        button_type: str | None = None,
 33        sale_scale: float = 1.0,
 34    ):
 35        self._position = position
 36        self._size = size
 37        self._scale = scale
 38
 39        if on_activate_call is None:
 40            on_activate_call = bui.WeakCall(self._default_on_activate_call)
 41        self._on_activate_call = on_activate_call
 42
 43        self._button = bui.buttonwidget(
 44            parent=parent,
 45            size=size,
 46            label='' if show_tickets else bui.Lstr(resource='storeText'),
 47            scale=scale,
 48            autoselect=True,
 49            on_activate_call=self._on_activate,
 50            transition_delay=transition_delay,
 51            color=color,
 52            button_type=button_type,
 53        )
 54
 55        self._title_text: bui.Widget | None
 56        self._ticket_text: bui.Widget | None
 57
 58        if show_tickets:
 59            self._title_text = bui.textwidget(
 60                parent=parent,
 61                position=(
 62                    position[0] + size[0] * 0.5 * scale,
 63                    position[1] + size[1] * 0.65 * scale,
 64                ),
 65                size=(0, 0),
 66                h_align='center',
 67                v_align='center',
 68                maxwidth=size[0] * scale * 0.65,
 69                text=bui.Lstr(resource='storeText'),
 70                draw_controller=self._button,
 71                scale=scale,
 72                transition_delay=transition_delay,
 73                color=textcolor,
 74            )
 75            self._ticket_text = bui.textwidget(
 76                parent=parent,
 77                size=(0, 0),
 78                h_align='center',
 79                v_align='center',
 80                maxwidth=size[0] * scale * 0.85,
 81                text='',
 82                color=(0.2, 1.0, 0.2),
 83                flatness=1.0,
 84                shadow=0.0,
 85                scale=scale * 0.6,
 86                transition_delay=transition_delay,
 87            )
 88        else:
 89            self._title_text = None
 90            self._ticket_text = None
 91
 92        self._circle_rad = 12 * scale
 93        self._circle_center = (0.0, 0.0)
 94        self._sale_circle_center = (0.0, 0.0)
 95
 96        self._available_purchase_backing = bui.imagewidget(
 97            parent=parent,
 98            color=(1, 0, 0),
 99            draw_controller=self._button,
100            size=(2.2 * self._circle_rad, 2.2 * self._circle_rad),
101            texture=bui.gettexture('circleShadow'),
102            transition_delay=transition_delay,
103        )
104        self._available_purchase_text = bui.textwidget(
105            parent=parent,
106            size=(0, 0),
107            h_align='center',
108            v_align='center',
109            text='',
110            draw_controller=self._button,
111            color=(1, 1, 1),
112            flatness=1.0,
113            shadow=1.0,
114            scale=0.6 * scale,
115            maxwidth=self._circle_rad * 1.4,
116            transition_delay=transition_delay,
117        )
118
119        self._sale_circle_rad = 18 * scale * sale_scale
120        self._sale_backing = bui.imagewidget(
121            parent=parent,
122            color=(0.5, 0, 1.0),
123            draw_controller=self._button,
124            size=(2 * self._sale_circle_rad, 2 * self._sale_circle_rad),
125            texture=bui.gettexture('circleZigZag'),
126            transition_delay=transition_delay,
127        )
128        self._sale_title_text = bui.textwidget(
129            parent=parent,
130            size=(0, 0),
131            h_align='center',
132            v_align='center',
133            draw_controller=self._button,
134            color=(0, 1, 0),
135            flatness=1.0,
136            shadow=0.0,
137            scale=0.5 * scale * sale_scale,
138            maxwidth=self._sale_circle_rad * 1.5,
139            transition_delay=transition_delay,
140        )
141        self._sale_time_text = bui.textwidget(
142            parent=parent,
143            size=(0, 0),
144            h_align='center',
145            v_align='center',
146            draw_controller=self._button,
147            color=(0, 1, 0),
148            flatness=1.0,
149            shadow=0.0,
150            scale=0.4 * scale * sale_scale,
151            maxwidth=self._sale_circle_rad * 1.5,
152            transition_delay=transition_delay,
153        )
154
155        self.set_position(position)
156        self._update_timer = bui.AppTimer(
157            1.0, bui.WeakCall(self._update), repeat=True
158        )
159        self._update()
def set_position(self, position: Sequence[float]) -> None:
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        bui.buttonwidget(edit=self._button, position=self._position)
180        if self._title_text is not None:
181            bui.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            bui.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        bui.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        bui.textwidget(
205            edit=self._available_purchase_text, position=self._circle_center
206        )
207
208        bui.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        bui.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        bui.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        )

Set the button position.

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

Return the underlying button widget.