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

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

Set the button position.

def get_button(self) -> _bauiv1.Widget:
247    def get_button(self) -> bui.Widget:
248        """Return the underlying button widget."""
249        return self._button

Return the underlying button widget.