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
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
241        raise RuntimeError('no longer wired up')
242
243        # StoreBrowserWindow(modal=True, origin_widget=self._button)
244
245    def get_button(self) -> bui.Widget:
246        """Return the underlying button widget."""
247        return self._button
248
249    def _update(self) -> None:
250        # pylint: disable=too-many-branches
251        # pylint: disable=cyclic-import
252        from babase import SpecialChar
253
254        plus = bui.app.plus
255        assert plus is not None
256        assert bui.app.classic is not None
257        store = bui.app.classic.store
258
259        if not self._button:
260            return  # Our instance may outlive our UI objects.
261
262        if self._ticket_text is not None:
263            if plus.get_v1_account_state() == 'signed_in':
264                sval = bui.charstr(SpecialChar.TICKET) + str(
265                    plus.get_v1_account_ticket_count()
266                )
267            else:
268                sval = '-'
269            bui.textwidget(edit=self._ticket_text, text=sval)
270        available_purchases = store.get_available_purchase_count()
271
272        # Old pro sale stuff..
273        sale_time = store.get_available_sale_time('extras')
274
275        # ..also look for new style sales.
276        if sale_time is None:
277            import datetime
278
279            sales_raw = plus.get_v1_account_misc_read_val('sales', {})
280            sale_times = []
281            try:
282                # Look at the current set of sales; filter any with time
283                # remaining that we don't own.
284                for sale_item, sale_info in list(sales_raw.items()):
285                    if not plus.get_purchased(sale_item):
286                        to_end = (
287                            datetime.datetime.fromtimestamp(
288                                sale_info['e'], datetime.UTC
289                            )
290                            - utc_now()
291                        ).total_seconds()
292                        if to_end > 0:
293                            sale_times.append(to_end)
294            except Exception:
295                logging.exception('Error parsing sales.')
296            if sale_times:
297                sale_time = int(min(sale_times) * 1000)
298
299        if sale_time is not None:
300            bui.textwidget(
301                edit=self._sale_title_text,
302                text=bui.Lstr(resource='store.saleText'),
303            )
304            bui.textwidget(
305                edit=self._sale_time_text,
306                text=bui.timestring(sale_time / 1000.0, centi=False),
307            )
308            bui.imagewidget(edit=self._sale_backing, opacity=1.0)
309            bui.imagewidget(edit=self._available_purchase_backing, opacity=1.0)
310            bui.textwidget(edit=self._available_purchase_text, text='')
311            bui.imagewidget(edit=self._available_purchase_backing, opacity=0.0)
312        else:
313            bui.imagewidget(edit=self._sale_backing, opacity=0.0)
314            bui.textwidget(edit=self._sale_time_text, text='')
315            bui.textwidget(edit=self._sale_title_text, text='')
316            if available_purchases > 0:
317                bui.textwidget(
318                    edit=self._available_purchase_text,
319                    text=str(available_purchases),
320                )
321                bui.imagewidget(
322                    edit=self._available_purchase_backing, opacity=1.0
323                )
324            else:
325                bui.textwidget(edit=self._available_purchase_text, text='')
326                bui.imagewidget(
327                    edit=self._available_purchase_backing, opacity=0.0
328                )
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
234        # from bauiv1lib.store.browser import StoreBrowserWindow
235
236        plus = bui.app.plus
237        assert plus is not None
238        if plus.get_v1_account_state() != 'signed_in':
239            show_sign_in_prompt()
240            return
241
242        raise RuntimeError('no longer wired up')
243
244        # StoreBrowserWindow(modal=True, origin_widget=self._button)
245
246    def get_button(self) -> bui.Widget:
247        """Return the underlying button widget."""
248        return self._button
249
250    def _update(self) -> None:
251        # pylint: disable=too-many-branches
252        # pylint: disable=cyclic-import
253        from babase import SpecialChar
254
255        plus = bui.app.plus
256        assert plus is not None
257        assert bui.app.classic is not None
258        store = bui.app.classic.store
259
260        if not self._button:
261            return  # Our instance may outlive our UI objects.
262
263        if self._ticket_text is not None:
264            if plus.get_v1_account_state() == 'signed_in':
265                sval = bui.charstr(SpecialChar.TICKET) + str(
266                    plus.get_v1_account_ticket_count()
267                )
268            else:
269                sval = '-'
270            bui.textwidget(edit=self._ticket_text, text=sval)
271        available_purchases = store.get_available_purchase_count()
272
273        # Old pro sale stuff..
274        sale_time = store.get_available_sale_time('extras')
275
276        # ..also look for new style sales.
277        if sale_time is None:
278            import datetime
279
280            sales_raw = plus.get_v1_account_misc_read_val('sales', {})
281            sale_times = []
282            try:
283                # Look at the current set of sales; filter any with time
284                # remaining that we don't own.
285                for sale_item, sale_info in list(sales_raw.items()):
286                    if not plus.get_purchased(sale_item):
287                        to_end = (
288                            datetime.datetime.fromtimestamp(
289                                sale_info['e'], datetime.UTC
290                            )
291                            - utc_now()
292                        ).total_seconds()
293                        if to_end > 0:
294                            sale_times.append(to_end)
295            except Exception:
296                logging.exception('Error parsing sales.')
297            if sale_times:
298                sale_time = int(min(sale_times) * 1000)
299
300        if sale_time is not None:
301            bui.textwidget(
302                edit=self._sale_title_text,
303                text=bui.Lstr(resource='store.saleText'),
304            )
305            bui.textwidget(
306                edit=self._sale_time_text,
307                text=bui.timestring(sale_time / 1000.0, centi=False),
308            )
309            bui.imagewidget(edit=self._sale_backing, opacity=1.0)
310            bui.imagewidget(edit=self._available_purchase_backing, opacity=1.0)
311            bui.textwidget(edit=self._available_purchase_text, text='')
312            bui.imagewidget(edit=self._available_purchase_backing, opacity=0.0)
313        else:
314            bui.imagewidget(edit=self._sale_backing, opacity=0.0)
315            bui.textwidget(edit=self._sale_time_text, text='')
316            bui.textwidget(edit=self._sale_title_text, text='')
317            if available_purchases > 0:
318                bui.textwidget(
319                    edit=self._available_purchase_text,
320                    text=str(available_purchases),
321                )
322                bui.imagewidget(
323                    edit=self._available_purchase_backing, opacity=1.0
324                )
325            else:
326                bui.textwidget(edit=self._available_purchase_text, text='')
327                bui.imagewidget(
328                    edit=self._available_purchase_backing, opacity=0.0
329                )

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:
246    def get_button(self) -> bui.Widget:
247        """Return the underlying button widget."""
248        return self._button

Return the underlying button widget.