bauiv1

Ballistica user interface api version 1

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Ballistica user interface api version 1"""
  4
  5# ba_meta require api 9
  6
  7# The stuff we expose here at the top level is our 'public' api.
  8# It should only be imported by code outside of this package or
  9# from 'if TYPE_CHECKING' blocks (which will not exec at runtime).
 10# Code within our package should import things directly from their
 11# submodules.
 12
 13from __future__ import annotations
 14
 15# pylint: disable=redefined-builtin
 16
 17import logging
 18
 19from efro.util import set_canonical_module_names
 20from babase import (
 21    add_clean_frame_callback,
 22    allows_ticket_sales,
 23    app,
 24    AppIntent,
 25    AppIntentDefault,
 26    AppIntentExec,
 27    AppMode,
 28    appname,
 29    appnameupper,
 30    apptime,
 31    AppTime,
 32    apptimer,
 33    AppTimer,
 34    Call,
 35    fullscreen_control_available,
 36    fullscreen_control_get,
 37    fullscreen_control_key_shortcut,
 38    fullscreen_control_set,
 39    charstr,
 40    clipboard_is_supported,
 41    clipboard_set_text,
 42    commit_app_config,
 43    ContextRef,
 44    displaytime,
 45    DisplayTime,
 46    displaytimer,
 47    DisplayTimer,
 48    do_once,
 49    existing,
 50    fade_screen,
 51    get_display_resolution,
 52    get_input_idle_time,
 53    get_ip_address_type,
 54    get_low_level_config_value,
 55    get_max_graphics_quality,
 56    get_remote_app_name,
 57    get_replays_dir,
 58    get_string_height,
 59    get_string_width,
 60    get_type_name,
 61    get_virtual_safe_area_size,
 62    get_virtual_screen_size,
 63    getclass,
 64    have_permission,
 65    in_logic_thread,
 66    in_main_menu,
 67    increment_analytics_count,
 68    is_browser_likely_available,
 69    is_xcode_build,
 70    lock_all_input,
 71    LoginAdapter,
 72    LoginInfo,
 73    Lstr,
 74    native_review_request,
 75    native_review_request_supported,
 76    NotFoundError,
 77    open_file_externally,
 78    open_url,
 79    overlay_web_browser_close,
 80    overlay_web_browser_is_open,
 81    overlay_web_browser_is_supported,
 82    overlay_web_browser_open_url,
 83    Permission,
 84    Plugin,
 85    PluginSpec,
 86    pushcall,
 87    quit,
 88    QuitType,
 89    request_permission,
 90    safecolor,
 91    screenmessage,
 92    set_analytics_screen,
 93    set_low_level_config_value,
 94    set_ui_input_device,
 95    SpecialChar,
 96    supports_max_fps,
 97    supports_vsync,
 98    supports_unicode_display,
 99    timestring,
100    UIScale,
101    unlock_all_input,
102    utc_now_cloud,
103    WeakCall,
104    workspaces_in_use,
105)
106
107from _bauiv1 import (
108    buttonwidget,
109    checkboxwidget,
110    columnwidget,
111    containerwidget,
112    get_qrcode_texture,
113    get_special_widget,
114    getmesh,
115    getsound,
116    gettexture,
117    hscrollwidget,
118    imagewidget,
119    Mesh,
120    root_ui_pause_updates,
121    root_ui_resume_updates,
122    rowwidget,
123    scrollwidget,
124    set_party_window_open,
125    spinnerwidget,
126    Sound,
127    Texture,
128    textwidget,
129    uibounds,
130    Widget,
131    widget,
132)
133from bauiv1._keyboard import Keyboard
134from bauiv1._uitypes import (
135    Window,
136    MainWindowState,
137    BasicMainWindowState,
138    uicleanupcheck,
139    MainWindow,
140)
141from bauiv1._appsubsystem import UIV1AppSubsystem
142
143__all__ = [
144    'add_clean_frame_callback',
145    'allows_ticket_sales',
146    'app',
147    'AppIntent',
148    'AppIntentDefault',
149    'AppIntentExec',
150    'AppMode',
151    'appname',
152    'appnameupper',
153    'appnameupper',
154    'apptime',
155    'AppTime',
156    'apptimer',
157    'AppTimer',
158    'BasicMainWindowState',
159    'buttonwidget',
160    'Call',
161    'fullscreen_control_available',
162    'fullscreen_control_get',
163    'fullscreen_control_key_shortcut',
164    'fullscreen_control_set',
165    'charstr',
166    'checkboxwidget',
167    'clipboard_is_supported',
168    'clipboard_set_text',
169    'columnwidget',
170    'commit_app_config',
171    'containerwidget',
172    'ContextRef',
173    'displaytime',
174    'DisplayTime',
175    'displaytimer',
176    'DisplayTimer',
177    'do_once',
178    'existing',
179    'fade_screen',
180    'get_display_resolution',
181    'get_input_idle_time',
182    'get_ip_address_type',
183    'get_low_level_config_value',
184    'get_max_graphics_quality',
185    'get_qrcode_texture',
186    'get_remote_app_name',
187    'get_replays_dir',
188    'get_special_widget',
189    'get_string_height',
190    'get_string_width',
191    'get_type_name',
192    'get_virtual_safe_area_size',
193    'get_virtual_screen_size',
194    'getclass',
195    'getmesh',
196    'getsound',
197    'gettexture',
198    'have_permission',
199    'hscrollwidget',
200    'imagewidget',
201    'in_logic_thread',
202    'in_main_menu',
203    'increment_analytics_count',
204    'is_browser_likely_available',
205    'is_xcode_build',
206    'Keyboard',
207    'lock_all_input',
208    'LoginAdapter',
209    'LoginInfo',
210    'Lstr',
211    'MainWindow',
212    'MainWindowState',
213    'Mesh',
214    'native_review_request',
215    'native_review_request_supported',
216    'NotFoundError',
217    'open_file_externally',
218    'open_url',
219    'overlay_web_browser_close',
220    'overlay_web_browser_is_open',
221    'overlay_web_browser_is_supported',
222    'overlay_web_browser_open_url',
223    'Permission',
224    'Plugin',
225    'PluginSpec',
226    'pushcall',
227    'quit',
228    'QuitType',
229    'request_permission',
230    'root_ui_pause_updates',
231    'root_ui_resume_updates',
232    'rowwidget',
233    'safecolor',
234    'screenmessage',
235    'scrollwidget',
236    'set_analytics_screen',
237    'set_low_level_config_value',
238    'set_party_window_open',
239    'set_ui_input_device',
240    'Sound',
241    'SpecialChar',
242    'spinnerwidget',
243    'supports_max_fps',
244    'supports_vsync',
245    'supports_unicode_display',
246    'Texture',
247    'textwidget',
248    'timestring',
249    'uibounds',
250    'uicleanupcheck',
251    'UIScale',
252    'UIV1AppSubsystem',
253    'unlock_all_input',
254    'utc_now_cloud',
255    'WeakCall',
256    'widget',
257    'Widget',
258    'Window',
259    'workspaces_in_use',
260]
261
262# We want stuff to show up as bauiv1.Foo instead of bauiv1._sub.Foo.
263set_canonical_module_names(globals())
264
265# Sanity check: we want to keep ballistica's dependencies and
266# bootstrapping order clearly defined; let's check a few particular
267# modules to make sure they never directly or indirectly import us
268# before their own execs complete.
269if __debug__:
270    for _mdl in 'babase', '_babase':
271        if not hasattr(__import__(_mdl), '_REACHED_END_OF_MODULE'):
272            logging.warning(
273                '%s was imported before %s finished importing;'
274                ' should not happen.',
275                __name__,
276                _mdl,
277            )
app = <babase.App object>
class AppIntent:
13class AppIntent:
14    """A high level directive given to the app.
15
16    Category: **App Classes**
17    """

A high level directive given to the app.

Category: App Classes

class AppIntentDefault(bauiv1.AppIntent):
20class AppIntentDefault(AppIntent):
21    """Tells the app to simply run in its default mode."""

Tells the app to simply run in its default mode.

class AppIntentExec(bauiv1.AppIntent):
24class AppIntentExec(AppIntent):
25    """Tells the app to exec some Python code."""
26
27    def __init__(self, code: str):
28        self.code = code

Tells the app to exec some Python code.

AppIntentExec(code: str)
27    def __init__(self, code: str):
28        self.code = code
code
class AppMode:
14class AppMode:
15    """A high level mode for the app.
16
17    Category: **App Classes**
18
19    """
20
21    @classmethod
22    def get_app_experience(cls) -> AppExperience:
23        """Return the overall experience provided by this mode."""
24        raise NotImplementedError('AppMode subclasses must override this.')
25
26    @classmethod
27    def can_handle_intent(cls, intent: AppIntent) -> bool:
28        """Return whether this mode can handle the provided intent.
29
30        For this to return True, the AppMode must claim to support the
31        provided intent (via its _can_handle_intent() method) AND the
32        AppExperience associated with the AppMode must be supported by
33        the current app and runtime environment.
34        """
35        # TODO: check AppExperience against current environment.
36        return cls._can_handle_intent(intent)
37
38    @classmethod
39    def _can_handle_intent(cls, intent: AppIntent) -> bool:
40        """Return whether our mode can handle the provided intent.
41
42        AppModes should override this to communicate what they can
43        handle. Note that AppExperience does not have to be considered
44        here; that is handled automatically by the can_handle_intent()
45        call.
46        """
47        raise NotImplementedError('AppMode subclasses must override this.')
48
49    def handle_intent(self, intent: AppIntent) -> None:
50        """Handle an intent."""
51        raise NotImplementedError('AppMode subclasses must override this.')
52
53    def on_activate(self) -> None:
54        """Called when the mode is being activated."""
55
56    def on_deactivate(self) -> None:
57        """Called when the mode is being deactivated."""
58
59    def on_app_active_changed(self) -> None:
60        """Called when ba*.app.active changes while this mode is active.
61
62        The app-mode may want to take action such as pausing a running
63        game in such cases.
64        """

A high level mode for the app.

Category: App Classes

@classmethod
def get_app_experience(cls) -> bacommon.app.AppExperience:
21    @classmethod
22    def get_app_experience(cls) -> AppExperience:
23        """Return the overall experience provided by this mode."""
24        raise NotImplementedError('AppMode subclasses must override this.')

Return the overall experience provided by this mode.

@classmethod
def can_handle_intent(cls, intent: AppIntent) -> bool:
26    @classmethod
27    def can_handle_intent(cls, intent: AppIntent) -> bool:
28        """Return whether this mode can handle the provided intent.
29
30        For this to return True, the AppMode must claim to support the
31        provided intent (via its _can_handle_intent() method) AND the
32        AppExperience associated with the AppMode must be supported by
33        the current app and runtime environment.
34        """
35        # TODO: check AppExperience against current environment.
36        return cls._can_handle_intent(intent)

Return whether this mode can handle the provided intent.

For this to return True, the AppMode must claim to support the provided intent (via its _can_handle_intent() method) AND the AppExperience associated with the AppMode must be supported by the current app and runtime environment.

def handle_intent(self, intent: AppIntent) -> None:
49    def handle_intent(self, intent: AppIntent) -> None:
50        """Handle an intent."""
51        raise NotImplementedError('AppMode subclasses must override this.')

Handle an intent.

def on_activate(self) -> None:
53    def on_activate(self) -> None:
54        """Called when the mode is being activated."""

Called when the mode is being activated.

def on_deactivate(self) -> None:
56    def on_deactivate(self) -> None:
57        """Called when the mode is being deactivated."""

Called when the mode is being deactivated.

def on_app_active_changed(self) -> None:
59    def on_app_active_changed(self) -> None:
60        """Called when ba*.app.active changes while this mode is active.
61
62        The app-mode may want to take action such as pausing a running
63        game in such cases.
64        """

Called when ba*.app.active changes while this mode is active.

The app-mode may want to take action such as pausing a running game in such cases.

def apptime() -> AppTime:
554def apptime() -> babase.AppTime:
555    """Return the current app-time in seconds.
556
557    Category: **General Utility Functions**
558
559    App-time is a monotonic time value; it starts at 0.0 when the app
560    launches and will never jump by large amounts or go backwards, even if
561    the system time changes. Its progression will pause when the app is in
562    a suspended state.
563
564    Note that the AppTime returned here is simply float; it just has a
565    unique type in the type-checker's eyes to help prevent it from being
566    accidentally used with time functionality expecting other time types.
567    """
568    import babase  # pylint: disable=cyclic-import
569
570    return babase.AppTime(0.0)

Return the current app-time in seconds.

Category: General Utility Functions

App-time is a monotonic time value; it starts at 0.0 when the app launches and will never jump by large amounts or go backwards, even if the system time changes. Its progression will pause when the app is in a suspended state.

Note that the AppTime returned here is simply float; it just has a unique type in the type-checker's eyes to help prevent it from being accidentally used with time functionality expecting other time types.

AppTime = AppTime
def apptimer(time: float, call: Callable[[], Any]) -> None:
573def apptimer(time: float, call: Callable[[], Any]) -> None:
574    """Schedule a callable object to run based on app-time.
575
576    Category: **General Utility Functions**
577
578    This function creates a one-off timer which cannot be canceled or
579    modified once created. If you require the ability to do so, or need
580    a repeating timer, use the babase.AppTimer class instead.
581
582    ##### Arguments
583    ###### time (float)
584    > Length of time in seconds that the timer will wait before firing.
585
586    ###### call (Callable[[], Any])
587    > A callable Python object. Note that the timer will retain a
588    strong reference to the callable for as long as the timer exists, so you
589    may want to look into concepts such as babase.WeakCall if that is not
590    desired.
591
592    ##### Examples
593    Print some stuff through time:
594    >>> babase.screenmessage('hello from now!')
595    >>> babase.apptimer(1.0, babase.Call(babase.screenmessage,
596                              'hello from the future!'))
597    >>> babase.apptimer(2.0, babase.Call(babase.screenmessage,
598    ...                       'hello from the future 2!'))
599    """
600    return None

Schedule a callable object to run based on app-time.

Category: General Utility Functions

This function creates a one-off timer which cannot be canceled or modified once created. If you require the ability to do so, or need a repeating timer, use the babase.AppTimer class instead.

Arguments
time (float)

Length of time in seconds that the timer will wait before firing.

call (Callable[[], Any])

A callable Python object. Note that the timer will retain a strong reference to the callable for as long as the timer exists, so you may want to look into concepts such as babase.WeakCall if that is not desired.

Examples

Print some stuff through time:

>>> babase.screenmessage('hello from now!')
>>> babase.apptimer(1.0, babase.Call(babase.screenmessage,
                          'hello from the future!'))
>>> babase.apptimer(2.0, babase.Call(babase.screenmessage,
...                       'hello from the future 2!'))
class AppTimer:
55class AppTimer:
56    """Timers are used to run code at later points in time.
57
58    Category: **General Utility Classes**
59
60    This class encapsulates a timer based on app-time.
61    The underlying timer will be destroyed when this object is no longer
62    referenced. If you do not want to worry about keeping a reference to
63    your timer around, use the babase.apptimer() function instead to get a
64    one-off timer.
65
66    ##### Arguments
67    ###### time
68    > Length of time in seconds that the timer will wait before firing.
69
70    ###### call
71    > A callable Python object. Remember that the timer will retain a
72    strong reference to the callable for as long as it exists, so you
73    may want to look into concepts such as babase.WeakCall if that is not
74    desired.
75
76    ###### repeat
77    > If True, the timer will fire repeatedly, with each successive
78    firing having the same delay as the first.
79
80    ##### Example
81
82    Use a Timer object to print repeatedly for a few seconds:
83    ... def say_it():
84    ...     babase.screenmessage('BADGER!')
85    ... def stop_saying_it():
86    ...     global g_timer
87    ...     g_timer = None
88    ...     babase.screenmessage('MUSHROOM MUSHROOM!')
89    ... # Create our timer; it will run as long as we have the self.t ref.
90    ... g_timer = babase.AppTimer(0.3, say_it, repeat=True)
91    ... # Now fire off a one-shot timer to kill it.
92    ... babase.apptimer(3.89, stop_saying_it)
93    """
94
95    def __init__(
96        self, time: float, call: Callable[[], Any], repeat: bool = False
97    ) -> None:
98        pass

Timers are used to run code at later points in time.

Category: General Utility Classes

This class encapsulates a timer based on app-time. The underlying timer will be destroyed when this object is no longer referenced. If you do not want to worry about keeping a reference to your timer around, use the babase.apptimer() function instead to get a one-off timer.

Arguments
time

Length of time in seconds that the timer will wait before firing.

call

A callable Python object. Remember that the timer will retain a strong reference to the callable for as long as it exists, so you may want to look into concepts such as babase.WeakCall if that is not desired.

repeat

If True, the timer will fire repeatedly, with each successive firing having the same delay as the first.

Example

Use a Timer object to print repeatedly for a few seconds: ... def say_it(): ... babase.screenmessage('BADGER!') ... def stop_saying_it(): ... global g_timer ... g_timer = None ... babase.screenmessage('MUSHROOM MUSHROOM!') ... # Create our timer; it will run as long as we have the self.t ref. ... g_timer = babase.AppTimer(0.3, say_it, repeat=True) ... # Now fire off a one-shot timer to kill it. ... babase.apptimer(3.89, stop_saying_it)

AppTimer(time: float, call: Callable[[], Any], repeat: bool = False)
95    def __init__(
96        self, time: float, call: Callable[[], Any], repeat: bool = False
97    ) -> None:
98        pass
class BasicMainWindowState(bauiv1.MainWindowState):
271class BasicMainWindowState(MainWindowState):
272    """A basic MainWindowState holding a lambda to recreate a MainWindow."""
273
274    def __init__(
275        self,
276        create_call: Callable[
277            [
278                Literal['in_right', 'in_left', 'in_scale'] | None,
279                bauiv1.Widget | None,
280            ],
281            bauiv1.MainWindow,
282        ],
283    ) -> None:
284        super().__init__()
285        self.create_call = create_call
286
287    @override
288    def create_window(
289        self,
290        transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
291        origin_widget: bauiv1.Widget | None = None,
292    ) -> bauiv1.MainWindow:
293        return self.create_call(transition, origin_widget)

A basic MainWindowState holding a lambda to recreate a MainWindow.

BasicMainWindowState( create_call: Callable[[Optional[Literal['in_right', 'in_left', 'in_scale']], _bauiv1.Widget | None], MainWindow])
274    def __init__(
275        self,
276        create_call: Callable[
277            [
278                Literal['in_right', 'in_left', 'in_scale'] | None,
279                bauiv1.Widget | None,
280            ],
281            bauiv1.MainWindow,
282        ],
283    ) -> None:
284        super().__init__()
285        self.create_call = create_call
create_call
@override
def create_window( self, transition: Optional[Literal['in_right', 'in_left', 'in_scale']] = None, origin_widget: _bauiv1.Widget | None = None) -> MainWindow:
287    @override
288    def create_window(
289        self,
290        transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
291        origin_widget: bauiv1.Widget | None = None,
292    ) -> bauiv1.MainWindow:
293        return self.create_call(transition, origin_widget)

Create a window based on this state.

WindowState child classes should override this to recreate their particular type of window.

def buttonwidget( *, edit: _bauiv1.Widget | None = None, parent: _bauiv1.Widget | None = None, id: str | None = None, size: Optional[Sequence[float]] = None, position: Optional[Sequence[float]] = None, on_activate_call: Optional[Callable] = None, label: str | Lstr | None = None, color: Optional[Sequence[float]] = None, down_widget: _bauiv1.Widget | None = None, up_widget: _bauiv1.Widget | None = None, left_widget: _bauiv1.Widget | None = None, right_widget: _bauiv1.Widget | None = None, texture: _bauiv1.Texture | None = None, text_scale: float | None = None, textcolor: Optional[Sequence[float]] = None, enable_sound: bool | None = None, mesh_transparent: _bauiv1.Mesh | None = None, mesh_opaque: _bauiv1.Mesh | None = None, repeat: bool | None = None, scale: float | None = None, transition_delay: float | None = None, on_select_call: Optional[Callable] = None, button_type: str | None = None, extra_touch_border_scale: float | None = None, selectable: bool | None = None, show_buffer_top: float | None = None, icon: _bauiv1.Texture | None = None, iconscale: float | None = None, icon_tint: float | None = None, icon_color: Optional[Sequence[float]] = None, autoselect: bool | None = None, mask_texture: _bauiv1.Texture | None = None, tint_texture: _bauiv1.Texture | None = None, tint_color: Optional[Sequence[float]] = None, tint2_color: Optional[Sequence[float]] = None, text_flatness: float | None = None, text_res_scale: float | None = None, enabled: bool | None = None) -> _bauiv1.Widget:
149def buttonwidget(
150    *,
151    edit: bauiv1.Widget | None = None,
152    parent: bauiv1.Widget | None = None,
153    id: str | None = None,
154    size: Sequence[float] | None = None,
155    position: Sequence[float] | None = None,
156    on_activate_call: Callable | None = None,
157    label: str | bauiv1.Lstr | None = None,
158    color: Sequence[float] | None = None,
159    down_widget: bauiv1.Widget | None = None,
160    up_widget: bauiv1.Widget | None = None,
161    left_widget: bauiv1.Widget | None = None,
162    right_widget: bauiv1.Widget | None = None,
163    texture: bauiv1.Texture | None = None,
164    text_scale: float | None = None,
165    textcolor: Sequence[float] | None = None,
166    enable_sound: bool | None = None,
167    mesh_transparent: bauiv1.Mesh | None = None,
168    mesh_opaque: bauiv1.Mesh | None = None,
169    repeat: bool | None = None,
170    scale: float | None = None,
171    transition_delay: float | None = None,
172    on_select_call: Callable | None = None,
173    button_type: str | None = None,
174    extra_touch_border_scale: float | None = None,
175    selectable: bool | None = None,
176    show_buffer_top: float | None = None,
177    icon: bauiv1.Texture | None = None,
178    iconscale: float | None = None,
179    icon_tint: float | None = None,
180    icon_color: Sequence[float] | None = None,
181    autoselect: bool | None = None,
182    mask_texture: bauiv1.Texture | None = None,
183    tint_texture: bauiv1.Texture | None = None,
184    tint_color: Sequence[float] | None = None,
185    tint2_color: Sequence[float] | None = None,
186    text_flatness: float | None = None,
187    text_res_scale: float | None = None,
188    enabled: bool | None = None,
189) -> bauiv1.Widget:
190    """Create or edit a button widget.
191
192    Category: **User Interface Functions**
193
194    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
195    a new one is created and returned. Arguments that are not set to None
196    are applied to the Widget.
197    """
198    import bauiv1  # pylint: disable=cyclic-import
199
200    return bauiv1.Widget()

Create or edit a button widget.

Category: User Interface Functions

Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise a new one is created and returned. Arguments that are not set to None are applied to the Widget.

Call = <class 'babase._general._Call'>
def charstr(char_id: SpecialChar) -> str:
618def charstr(char_id: babase.SpecialChar) -> str:
619    """Get a unicode string representing a special character.
620
621    Category: **General Utility Functions**
622
623    Note that these utilize the private-use block of unicode characters
624    (U+E000-U+F8FF) and are specific to the game; exporting or rendering
625    them elsewhere will be meaningless.
626
627    See babase.SpecialChar for the list of available characters.
628    """
629    return str()

Get a unicode string representing a special character.

Category: General Utility Functions

Note that these utilize the private-use block of unicode characters (U+E000-U+F8FF) and are specific to the game; exporting or rendering them elsewhere will be meaningless.

See babase.SpecialChar for the list of available characters.

def checkboxwidget( *, edit: _bauiv1.Widget | None = None, parent: _bauiv1.Widget | None = None, size: Optional[Sequence[float]] = None, position: Optional[Sequence[float]] = None, text: str | Lstr | None = None, value: bool | None = None, on_value_change_call: Optional[Callable[[bool], NoneType]] = None, on_select_call: Optional[Callable[[], NoneType]] = None, text_scale: float | None = None, textcolor: Optional[Sequence[float]] = None, scale: float | None = None, is_radio_button: bool | None = None, maxwidth: float | None = None, autoselect: bool | None = None, color: Optional[Sequence[float]] = None) -> _bauiv1.Widget:
203def checkboxwidget(
204    *,
205    edit: bauiv1.Widget | None = None,
206    parent: bauiv1.Widget | None = None,
207    size: Sequence[float] | None = None,
208    position: Sequence[float] | None = None,
209    text: str | bauiv1.Lstr | None = None,
210    value: bool | None = None,
211    on_value_change_call: Callable[[bool], None] | None = None,
212    on_select_call: Callable[[], None] | None = None,
213    text_scale: float | None = None,
214    textcolor: Sequence[float] | None = None,
215    scale: float | None = None,
216    is_radio_button: bool | None = None,
217    maxwidth: float | None = None,
218    autoselect: bool | None = None,
219    color: Sequence[float] | None = None,
220) -> bauiv1.Widget:
221    """Create or edit a check-box widget.
222
223    Category: **User Interface Functions**
224
225    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
226    a new one is created and returned. Arguments that are not set to None
227    are applied to the Widget.
228    """
229    import bauiv1  # pylint: disable=cyclic-import
230
231    return bauiv1.Widget()

Create or edit a check-box widget.

Category: User Interface Functions

Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise a new one is created and returned. Arguments that are not set to None are applied to the Widget.

def clipboard_is_supported() -> bool:
654def clipboard_is_supported() -> bool:
655    """Return whether this platform supports clipboard operations at all.
656
657    Category: **General Utility Functions**
658
659    If this returns False, UIs should not show 'copy to clipboard'
660    buttons, etc.
661    """
662    return bool()

Return whether this platform supports clipboard operations at all.

Category: General Utility Functions

If this returns False, UIs should not show 'copy to clipboard' buttons, etc.

def clipboard_set_text(value: str) -> None:
665def clipboard_set_text(value: str) -> None:
666    """Copy a string to the system clipboard.
667
668    Category: **General Utility Functions**
669
670    Ensure that babase.clipboard_is_supported() returns True before adding
671     buttons/etc. that make use of this functionality.
672    """
673    return None

Copy a string to the system clipboard.

Category: General Utility Functions

Ensure that babase.clipboard_is_supported() returns True before adding buttons/etc. that make use of this functionality.

def columnwidget( *, edit: _bauiv1.Widget | None = None, parent: _bauiv1.Widget | None = None, size: Optional[Sequence[float]] = None, position: Optional[Sequence[float]] = None, background: bool | None = None, selected_child: _bauiv1.Widget | None = None, visible_child: _bauiv1.Widget | None = None, single_depth: bool | None = None, print_list_exit_instructions: bool | None = None, left_border: float | None = None, top_border: float | None = None, bottom_border: float | None = None, selection_loops_to_parent: bool | None = None, border: float | None = None, margin: float | None = None, claims_left_right: bool | None = None) -> _bauiv1.Widget:
234def columnwidget(
235    *,
236    edit: bauiv1.Widget | None = None,
237    parent: bauiv1.Widget | None = None,
238    size: Sequence[float] | None = None,
239    position: Sequence[float] | None = None,
240    background: bool | None = None,
241    selected_child: bauiv1.Widget | None = None,
242    visible_child: bauiv1.Widget | None = None,
243    single_depth: bool | None = None,
244    print_list_exit_instructions: bool | None = None,
245    left_border: float | None = None,
246    top_border: float | None = None,
247    bottom_border: float | None = None,
248    selection_loops_to_parent: bool | None = None,
249    border: float | None = None,
250    margin: float | None = None,
251    claims_left_right: bool | None = None,
252) -> bauiv1.Widget:
253    """Create or edit a column widget.
254
255    Category: **User Interface Functions**
256
257    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
258    a new one is created and returned. Arguments that are not set to None
259    are applied to the Widget.
260    """
261    import bauiv1  # pylint: disable=cyclic-import
262
263    return bauiv1.Widget()

Create or edit a column widget.

Category: User Interface Functions

Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise a new one is created and returned. Arguments that are not set to None are applied to the Widget.

def containerwidget( *, edit: _bauiv1.Widget | None = None, parent: _bauiv1.Widget | None = None, id: str | None = None, size: Optional[Sequence[float]] = None, position: Optional[Sequence[float]] = None, background: bool | None = None, selected_child: _bauiv1.Widget | None = None, transition: str | None = None, cancel_button: _bauiv1.Widget | None = None, start_button: _bauiv1.Widget | None = None, root_selectable: bool | None = None, on_activate_call: Optional[Callable[[], NoneType]] = None, claims_left_right: bool | None = None, selection_loops: bool | None = None, selection_loops_to_parent: bool | None = None, scale: float | None = None, on_outside_click_call: Optional[Callable[[], NoneType]] = None, single_depth: bool | None = None, visible_child: _bauiv1.Widget | None = None, stack_offset: Optional[Sequence[float]] = None, color: Optional[Sequence[float]] = None, on_cancel_call: Optional[Callable[[], NoneType]] = None, print_list_exit_instructions: bool | None = None, click_activate: bool | None = None, always_highlight: bool | None = None, selectable: bool | None = None, scale_origin_stack_offset: Optional[Sequence[float]] = None, toolbar_visibility: Optional[Literal['menu_minimal', 'menu_minimal_no_back', 'menu_full', 'menu_full_no_back', 'menu_store', 'menu_store_no_back', 'menu_in_game', 'menu_tokens', 'get_tokens', 'no_menu_minimal', 'inherit']] = None, on_select_call: Optional[Callable[[], NoneType]] = None, claim_outside_clicks: bool | None = None, claims_up_down: bool | None = None) -> _bauiv1.Widget:
266def containerwidget(
267    *,
268    edit: bauiv1.Widget | None = None,
269    parent: bauiv1.Widget | None = None,
270    id: str | None = None,
271    size: Sequence[float] | None = None,
272    position: Sequence[float] | None = None,
273    background: bool | None = None,
274    selected_child: bauiv1.Widget | None = None,
275    transition: str | None = None,
276    cancel_button: bauiv1.Widget | None = None,
277    start_button: bauiv1.Widget | None = None,
278    root_selectable: bool | None = None,
279    on_activate_call: Callable[[], None] | None = None,
280    claims_left_right: bool | None = None,
281    selection_loops: bool | None = None,
282    selection_loops_to_parent: bool | None = None,
283    scale: float | None = None,
284    on_outside_click_call: Callable[[], None] | None = None,
285    single_depth: bool | None = None,
286    visible_child: bauiv1.Widget | None = None,
287    stack_offset: Sequence[float] | None = None,
288    color: Sequence[float] | None = None,
289    on_cancel_call: Callable[[], None] | None = None,
290    print_list_exit_instructions: bool | None = None,
291    click_activate: bool | None = None,
292    always_highlight: bool | None = None,
293    selectable: bool | None = None,
294    scale_origin_stack_offset: Sequence[float] | None = None,
295    toolbar_visibility: (
296        Literal[
297            'menu_minimal',
298            'menu_minimal_no_back',
299            'menu_full',
300            'menu_full_no_back',
301            'menu_store',
302            'menu_store_no_back',
303            'menu_in_game',
304            'menu_tokens',
305            'get_tokens',
306            'no_menu_minimal',
307            'inherit',
308        ]
309        | None
310    ) = None,
311    on_select_call: Callable[[], None] | None = None,
312    claim_outside_clicks: bool | None = None,
313    claims_up_down: bool | None = None,
314) -> bauiv1.Widget:
315    """Create or edit a container widget.
316
317    Category: **User Interface Functions**
318
319    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
320    a new one is created and returned. Arguments that are not set to None
321    are applied to the Widget.
322    """
323    import bauiv1  # pylint: disable=cyclic-import
324
325    return bauiv1.Widget()

Create or edit a container widget.

Category: User Interface Functions

Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise a new one is created and returned. Arguments that are not set to None are applied to the Widget.

class ContextRef:
150class ContextRef:
151    """Store or use a ballistica context.
152
153    Category: **General Utility Classes**
154
155    Many operations such as bascenev1.newnode() or bascenev1.gettexture()
156    operate implicitly on a current 'context'. A context is some sort of
157    state that functionality can implicitly use. Context determines, for
158    example, which scene nodes or textures get added to without having to
159    specify it explicitly in the newnode()/gettexture() call. Contexts can
160    also affect object lifecycles; for example a babase.ContextCall will
161    become a no-op when the context it was created in is destroyed.
162
163    In general, if you are a modder, you should not need to worry about
164    contexts; mod code should mostly be getting run in the correct
165    context and timers and other callbacks will take care of saving
166    and restoring contexts automatically. There may be rare cases,
167    however, where you need to deal directly with contexts, and that is
168    where this class comes in.
169
170    Creating a babase.ContextRef() will capture a reference to the current
171    context. Other modules may provide ways to access their contexts; for
172    example a bascenev1.Activity instance has a 'context' attribute. You
173    can also use babase.ContextRef.empty() to create a reference to *no*
174    context. Some code such as UI calls may expect this and may complain
175    if you try to use them within a context.
176
177    ##### Usage
178    ContextRefs are generally used with the Python 'with' statement, which
179    sets the context they point to as current on entry and resets it to
180    the previous value on exit.
181
182    ##### Example
183    Explicitly create a few UI bits with no context set.
184    (UI stuff may complain if called within a context):
185    >>> with bui.ContextRef.empty():
186    ...     my_container = bui.containerwidget()
187    """
188
189    def __init__(
190        self,
191    ) -> None:
192        pass
193
194    def __enter__(self) -> None:
195        """Support for "with" statement."""
196        pass
197
198    def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> Any:
199        """Support for "with" statement."""
200        pass
201
202    @classmethod
203    def empty(cls) -> ContextRef:
204        """Return a ContextRef pointing to no context.
205
206        This is useful when code should be run free of a context.
207        For example, UI code generally insists on being run this way.
208        Otherwise, callbacks set on the UI could inadvertently stop working
209        due to a game activity ending, which would be unintuitive behavior.
210        """
211        return ContextRef()
212
213    def is_empty(self) -> bool:
214        """Whether the context was created as empty."""
215        return bool()
216
217    def is_expired(self) -> bool:
218        """Whether the context has expired."""
219        return bool()

Store or use a ballistica context.

Category: General Utility Classes

Many operations such as bascenev1.newnode() or bascenev1.gettexture() operate implicitly on a current 'context'. A context is some sort of state that functionality can implicitly use. Context determines, for example, which scene nodes or textures get added to without having to specify it explicitly in the newnode()/gettexture() call. Contexts can also affect object lifecycles; for example a babase.ContextCall will become a no-op when the context it was created in is destroyed.

In general, if you are a modder, you should not need to worry about contexts; mod code should mostly be getting run in the correct context and timers and other callbacks will take care of saving and restoring contexts automatically. There may be rare cases, however, where you need to deal directly with contexts, and that is where this class comes in.

Creating a babase.ContextRef() will capture a reference to the current context. Other modules may provide ways to access their contexts; for example a bascenev1.Activity instance has a 'context' attribute. You can also use babase.ContextRef.empty() to create a reference to no context. Some code such as UI calls may expect this and may complain if you try to use them within a context.

Usage

ContextRefs are generally used with the Python 'with' statement, which sets the context they point to as current on entry and resets it to the previous value on exit.

Example

Explicitly create a few UI bits with no context set. (UI stuff may complain if called within a context):

>>> with bui.ContextRef.empty():
...     my_container = bui.containerwidget()
@classmethod
def empty(cls) -> _babase.ContextRef:
202    @classmethod
203    def empty(cls) -> ContextRef:
204        """Return a ContextRef pointing to no context.
205
206        This is useful when code should be run free of a context.
207        For example, UI code generally insists on being run this way.
208        Otherwise, callbacks set on the UI could inadvertently stop working
209        due to a game activity ending, which would be unintuitive behavior.
210        """
211        return ContextRef()

Return a ContextRef pointing to no context.

This is useful when code should be run free of a context. For example, UI code generally insists on being run this way. Otherwise, callbacks set on the UI could inadvertently stop working due to a game activity ending, which would be unintuitive behavior.

def is_empty(self) -> bool:
213    def is_empty(self) -> bool:
214        """Whether the context was created as empty."""
215        return bool()

Whether the context was created as empty.

def is_expired(self) -> bool:
217    def is_expired(self) -> bool:
218        """Whether the context has expired."""
219        return bool()

Whether the context has expired.

def displaytime() -> DisplayTime:
759def displaytime() -> babase.DisplayTime:
760    """Return the current display-time in seconds.
761
762    Category: **General Utility Functions**
763
764    Display-time is a time value intended to be used for animation and other
765    visual purposes. It will generally increment by a consistent amount each
766    frame. It will pass at an overall similar rate to AppTime, but trades
767    accuracy for smoothness.
768
769    Note that the value returned here is simply a float; it just has a
770    unique type in the type-checker's eyes to help prevent it from being
771    accidentally used with time functionality expecting other time types.
772    """
773    import babase  # pylint: disable=cyclic-import
774
775    return babase.DisplayTime(0.0)

Return the current display-time in seconds.

Category: General Utility Functions

Display-time is a time value intended to be used for animation and other visual purposes. It will generally increment by a consistent amount each frame. It will pass at an overall similar rate to AppTime, but trades accuracy for smoothness.

Note that the value returned here is simply a float; it just has a unique type in the type-checker's eyes to help prevent it from being accidentally used with time functionality expecting other time types.

DisplayTime = DisplayTime
def displaytimer(time: float, call: Callable[[], Any]) -> None:
778def displaytimer(time: float, call: Callable[[], Any]) -> None:
779    """Schedule a callable object to run based on display-time.
780
781    Category: **General Utility Functions**
782
783    This function creates a one-off timer which cannot be canceled or
784    modified once created. If you require the ability to do so, or need
785    a repeating timer, use the babase.DisplayTimer class instead.
786
787    Display-time is a time value intended to be used for animation and other
788    visual purposes. It will generally increment by a consistent amount each
789    frame. It will pass at an overall similar rate to AppTime, but trades
790    accuracy for smoothness.
791
792    ##### Arguments
793    ###### time (float)
794    > Length of time in seconds that the timer will wait before firing.
795
796    ###### call (Callable[[], Any])
797    > A callable Python object. Note that the timer will retain a
798    strong reference to the callable for as long as the timer exists, so you
799    may want to look into concepts such as babase.WeakCall if that is not
800    desired.
801
802    ##### Examples
803    Print some stuff through time:
804    >>> babase.screenmessage('hello from now!')
805    >>> babase.displaytimer(1.0, babase.Call(babase.screenmessage,
806    ...                       'hello from the future!'))
807    >>> babase.displaytimer(2.0, babase.Call(babase.screenmessage,
808    ...                       'hello from the future 2!'))
809    """
810    return None

Schedule a callable object to run based on display-time.

Category: General Utility Functions

This function creates a one-off timer which cannot be canceled or modified once created. If you require the ability to do so, or need a repeating timer, use the babase.DisplayTimer class instead.

Display-time is a time value intended to be used for animation and other visual purposes. It will generally increment by a consistent amount each frame. It will pass at an overall similar rate to AppTime, but trades accuracy for smoothness.

Arguments
time (float)

Length of time in seconds that the timer will wait before firing.

call (Callable[[], Any])

A callable Python object. Note that the timer will retain a strong reference to the callable for as long as the timer exists, so you may want to look into concepts such as babase.WeakCall if that is not desired.

Examples

Print some stuff through time:

>>> babase.screenmessage('hello from now!')
>>> babase.displaytimer(1.0, babase.Call(babase.screenmessage,
...                       'hello from the future!'))
>>> babase.displaytimer(2.0, babase.Call(babase.screenmessage,
...                       'hello from the future 2!'))
class DisplayTimer:
222class DisplayTimer:
223    """Timers are used to run code at later points in time.
224
225    Category: **General Utility Classes**
226
227    This class encapsulates a timer based on display-time.
228    The underlying timer will be destroyed when this object is no longer
229    referenced. If you do not want to worry about keeping a reference to
230    your timer around, use the babase.displaytimer() function instead to get a
231    one-off timer.
232
233    Display-time is a time value intended to be used for animation and
234    other visual purposes. It will generally increment by a consistent
235    amount each frame. It will pass at an overall similar rate to AppTime,
236    but trades accuracy for smoothness.
237
238    ##### Arguments
239    ###### time
240    > Length of time in seconds that the timer will wait before firing.
241
242    ###### call
243    > A callable Python object. Remember that the timer will retain a
244    strong reference to the callable for as long as it exists, so you
245    may want to look into concepts such as babase.WeakCall if that is not
246    desired.
247
248    ###### repeat
249    > If True, the timer will fire repeatedly, with each successive
250    firing having the same delay as the first.
251
252    ##### Example
253
254    Use a Timer object to print repeatedly for a few seconds:
255    ... def say_it():
256    ...     babase.screenmessage('BADGER!')
257    ... def stop_saying_it():
258    ...     global g_timer
259    ...     g_timer = None
260    ...     babase.screenmessage('MUSHROOM MUSHROOM!')
261    ... # Create our timer; it will run as long as we have the self.t ref.
262    ... g_timer = babase.DisplayTimer(0.3, say_it, repeat=True)
263    ... # Now fire off a one-shot timer to kill it.
264    ... babase.displaytimer(3.89, stop_saying_it)
265    """
266
267    def __init__(
268        self, time: float, call: Callable[[], Any], repeat: bool = False
269    ) -> None:
270        pass

Timers are used to run code at later points in time.

Category: General Utility Classes

This class encapsulates a timer based on display-time. The underlying timer will be destroyed when this object is no longer referenced. If you do not want to worry about keeping a reference to your timer around, use the babase.displaytimer() function instead to get a one-off timer.

Display-time is a time value intended to be used for animation and other visual purposes. It will generally increment by a consistent amount each frame. It will pass at an overall similar rate to AppTime, but trades accuracy for smoothness.

Arguments
time

Length of time in seconds that the timer will wait before firing.

call

A callable Python object. Remember that the timer will retain a strong reference to the callable for as long as it exists, so you may want to look into concepts such as babase.WeakCall if that is not desired.

repeat

If True, the timer will fire repeatedly, with each successive firing having the same delay as the first.

Example

Use a Timer object to print repeatedly for a few seconds: ... def say_it(): ... babase.screenmessage('BADGER!') ... def stop_saying_it(): ... global g_timer ... g_timer = None ... babase.screenmessage('MUSHROOM MUSHROOM!') ... # Create our timer; it will run as long as we have the self.t ref. ... g_timer = babase.DisplayTimer(0.3, say_it, repeat=True) ... # Now fire off a one-shot timer to kill it. ... babase.displaytimer(3.89, stop_saying_it)

DisplayTimer(time: float, call: Callable[[], Any], repeat: bool = False)
267    def __init__(
268        self, time: float, call: Callable[[], Any], repeat: bool = False
269    ) -> None:
270        pass
def do_once() -> bool:
818def do_once() -> bool:
819    """Return whether this is the first time running a line of code.
820
821    Category: **General Utility Functions**
822
823    This is used by 'print_once()' type calls to keep from overflowing
824    logs. The call functions by registering the filename and line where
825    The call is made from.  Returns True if this location has not been
826    registered already, and False if it has.
827
828    ##### Example
829    This print will only fire for the first loop iteration:
830    >>> for i in range(10):
831    ... if babase.do_once():
832    ...     print('HelloWorld once from loop!')
833    """
834    return bool()

Return whether this is the first time running a line of code.

Category: General Utility Functions

This is used by 'print_once()' type calls to keep from overflowing logs. The call functions by registering the filename and line where The call is made from. Returns True if this location has not been registered already, and False if it has.

Example

This print will only fire for the first loop iteration:

>>> for i in range(10):
... if babase.do_once():
...     print('HelloWorld once from loop!')
def existing(obj: Optional[~ExistableT]) -> Optional[~ExistableT]:
51def existing(obj: ExistableT | None) -> ExistableT | None:
52    """Convert invalid references to None for any babase.Existable object.
53
54    Category: **Gameplay Functions**
55
56    To best support type checking, it is important that invalid references
57    not be passed around and instead get converted to values of None.
58    That way the type checker can properly flag attempts to pass possibly-dead
59    objects (FooType | None) into functions expecting only live ones
60    (FooType), etc. This call can be used on any 'existable' object
61    (one with an exists() method) and will convert it to a None value
62    if it does not exist.
63
64    For more info, see notes on 'existables' here:
65    https://ballistica.net/wiki/Coding-Style-Guide
66    """
67    assert obj is None or hasattr(obj, 'exists'), f'No "exists" attr on {obj}.'
68    return obj if obj is not None and obj.exists() else None

Convert invalid references to None for any babase.Existable object.

Category: Gameplay Functions

To best support type checking, it is important that invalid references not be passed around and instead get converted to values of None. That way the type checker can properly flag attempts to pass possibly-dead objects (FooType | None) into functions expecting only live ones (FooType), etc. This call can be used on any 'existable' object (one with an exists() method) and will convert it to a None value if it does not exist.

For more info, see notes on 'existables' here: https://ballistica.net/wiki/Coding-Style-Guide

def get_input_idle_time() -> float:
1003def get_input_idle_time() -> float:
1004    """Return seconds since any local input occurred (touch, keypress, etc.)."""
1005    return float()

Return seconds since any local input occurred (touch, keypress, etc.).

def get_ip_address_type(addr: str) -> socket.AddressFamily:
45def get_ip_address_type(addr: str) -> socket.AddressFamily:
46    """Return socket.AF_INET6 or socket.AF_INET4 for the provided address."""
47
48    version = ipaddress.ip_address(addr).version
49    if version == 4:
50        return socket.AF_INET
51    assert version == 6
52    return socket.AF_INET6

Return socket.AF_INET6 or socket.AF_INET4 for the provided address.

def get_qrcode_texture(url: str) -> _bauiv1.Texture:
328def get_qrcode_texture(url: str) -> bauiv1.Texture:
329    """Return a QR code texture.
330
331    The provided url must be 64 bytes or less.
332    """
333    import bauiv1  # pylint: disable=cyclic-import
334
335    return bauiv1.Texture()

Return a QR code texture.

The provided url must be 64 bytes or less.

def get_type_name(cls: type) -> str:
112def get_type_name(cls: type) -> str:
113    """Return a full type name including module for a class."""
114    return f'{cls.__module__}.{cls.__name__}'

Return a full type name including module for a class.

def getclass( name: str, subclassof: type[~T], check_sdlib_modulename_clash: bool = False) -> type[~T]:
71def getclass(
72    name: str, subclassof: type[T], check_sdlib_modulename_clash: bool = False
73) -> type[T]:
74    """Given a full class name such as foo.bar.MyClass, return the class.
75
76    Category: **General Utility Functions**
77
78    The class will be checked to make sure it is a subclass of the provided
79    'subclassof' class, and a TypeError will be raised if not.
80    """
81    import importlib
82
83    splits = name.split('.')
84    modulename = '.'.join(splits[:-1])
85    classname = splits[-1]
86    if modulename in sys.stdlib_module_names and check_sdlib_modulename_clash:
87        raise Exception(f'{modulename} is an inbuilt module.')
88    module = importlib.import_module(modulename)
89    cls: type = getattr(module, classname)
90
91    if not issubclass(cls, subclassof):
92        raise TypeError(f'{name} is not a subclass of {subclassof}.')
93    return cls

Given a full class name such as foo.bar.MyClass, return the class.

Category: General Utility Functions

The class will be checked to make sure it is a subclass of the provided 'subclassof' class, and a TypeError will be raised if not.

def getmesh(name: str) -> _bauiv1.Mesh:
366def getmesh(name: str) -> bauiv1.Mesh:
367    """Load a mesh for use solely in the local user interface."""
368    import bauiv1  # pylint: disable=cyclic-import
369
370    return bauiv1.Mesh()

Load a mesh for use solely in the local user interface.

def getsound(name: str) -> _bauiv1.Sound:
373def getsound(name: str) -> bauiv1.Sound:
374    """Load a sound for use in the ui."""
375    import bauiv1  # pylint: disable=cyclic-import
376
377    return bauiv1.Sound()

Load a sound for use in the ui.

def gettexture(name: str) -> _bauiv1.Texture:
380def gettexture(name: str) -> bauiv1.Texture:
381    """Load a texture for use in the ui."""
382    import bauiv1  # pylint: disable=cyclic-import
383
384    return bauiv1.Texture()

Load a texture for use in the ui.

def hscrollwidget( *, edit: _bauiv1.Widget | None = None, parent: _bauiv1.Widget | None = None, size: Optional[Sequence[float]] = None, position: Optional[Sequence[float]] = None, background: bool | None = None, selected_child: _bauiv1.Widget | None = None, capture_arrows: bool | None = None, on_select_call: Optional[Callable[[], NoneType]] = None, center_small_content: bool | None = None, color: Optional[Sequence[float]] = None, highlight: bool | None = None, border_opacity: float | None = None, simple_culling_h: float | None = None, claims_left_right: bool | None = None, claims_up_down: bool | None = None) -> _bauiv1.Widget:
387def hscrollwidget(
388    *,
389    edit: bauiv1.Widget | None = None,
390    parent: bauiv1.Widget | None = None,
391    size: Sequence[float] | None = None,
392    position: Sequence[float] | None = None,
393    background: bool | None = None,
394    selected_child: bauiv1.Widget | None = None,
395    capture_arrows: bool | None = None,
396    on_select_call: Callable[[], None] | None = None,
397    center_small_content: bool | None = None,
398    color: Sequence[float] | None = None,
399    highlight: bool | None = None,
400    border_opacity: float | None = None,
401    simple_culling_h: float | None = None,
402    claims_left_right: bool | None = None,
403    claims_up_down: bool | None = None,
404) -> bauiv1.Widget:
405    """Create or edit a horizontal scroll widget.
406
407    Category: **User Interface Functions**
408
409    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
410    a new one is created and returned. Arguments that are not set to None
411    are applied to the Widget.
412    """
413    import bauiv1  # pylint: disable=cyclic-import
414
415    return bauiv1.Widget()

Create or edit a horizontal scroll widget.

Category: User Interface Functions

Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise a new one is created and returned. Arguments that are not set to None are applied to the Widget.

def imagewidget( *, edit: _bauiv1.Widget | None = None, parent: _bauiv1.Widget | None = None, size: Optional[Sequence[float]] = None, position: Optional[Sequence[float]] = None, color: Optional[Sequence[float]] = None, texture: _bauiv1.Texture | None = None, opacity: float | None = None, mesh_transparent: _bauiv1.Mesh | None = None, mesh_opaque: _bauiv1.Mesh | None = None, has_alpha_channel: bool = True, tint_texture: _bauiv1.Texture | None = None, tint_color: Optional[Sequence[float]] = None, transition_delay: float | None = None, draw_controller: _bauiv1.Widget | None = None, tint2_color: Optional[Sequence[float]] = None, tilt_scale: float | None = None, mask_texture: _bauiv1.Texture | None = None, radial_amount: float | None = None, draw_controller_mult: float | None = None) -> _bauiv1.Widget:
418def imagewidget(
419    *,
420    edit: bauiv1.Widget | None = None,
421    parent: bauiv1.Widget | None = None,
422    size: Sequence[float] | None = None,
423    position: Sequence[float] | None = None,
424    color: Sequence[float] | None = None,
425    texture: bauiv1.Texture | None = None,
426    opacity: float | None = None,
427    mesh_transparent: bauiv1.Mesh | None = None,
428    mesh_opaque: bauiv1.Mesh | None = None,
429    has_alpha_channel: bool = True,
430    tint_texture: bauiv1.Texture | None = None,
431    tint_color: Sequence[float] | None = None,
432    transition_delay: float | None = None,
433    draw_controller: bauiv1.Widget | None = None,
434    tint2_color: Sequence[float] | None = None,
435    tilt_scale: float | None = None,
436    mask_texture: bauiv1.Texture | None = None,
437    radial_amount: float | None = None,
438    draw_controller_mult: float | None = None,
439) -> bauiv1.Widget:
440    """Create or edit an image widget.
441
442    Category: **User Interface Functions**
443
444    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
445    a new one is created and returned. Arguments that are not set to None
446    are applied to the Widget.
447    """
448    import bauiv1  # pylint: disable=cyclic-import
449
450    return bauiv1.Widget()

Create or edit an image widget.

Category: User Interface Functions

Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise a new one is created and returned. Arguments that are not set to None are applied to the Widget.

def is_browser_likely_available() -> bool:
40def is_browser_likely_available() -> bool:
41    """Return whether a browser likely exists on the current device.
42
43    category: General Utility Functions
44
45    If this returns False you may want to avoid calling babase.open_url()
46    with any lengthy addresses. (babase.open_url() will display an address
47    as a string in a window if unable to bring up a browser, but that
48    is only useful for simple URLs.)
49    """
50    app = _babase.app
51
52    if app.classic is None:
53        logging.warning(
54            'is_browser_likely_available() needs to be updated'
55            ' to work without classic.'
56        )
57        return True
58
59    platform = app.classic.platform
60    hastouchscreen = _babase.hastouchscreen()
61
62    # If we're on a vr device or an android device with no touchscreen,
63    # assume no browser.
64    # FIXME: Might not be the case anymore; should make this definable
65    #  at the platform level.
66    if app.env.vr or (platform == 'android' and not hastouchscreen):
67        return False
68
69    # Anywhere else assume we've got one.
70    return True

Return whether a browser likely exists on the current device.

category: General Utility Functions

If this returns False you may want to avoid calling babase.open_url() with any lengthy addresses. (babase.open_url() will display an address as a string in a window if unable to bring up a browser, but that is only useful for simple URLs.)

class Keyboard:
14class Keyboard:
15    """Chars definitions for on-screen keyboard.
16
17    Category: **App Classes**
18
19    Keyboards are discoverable by the meta-tag system
20    and the user can select which one they want to use.
21    On-screen keyboard uses chars from active babase.Keyboard.
22    """
23
24    name: str
25    """Displays when user selecting this keyboard."""
26
27    chars: list[tuple[str, ...]]
28    """Used for row/column lengths."""
29
30    pages: dict[str, tuple[str, ...]]
31    """Extra chars like emojis."""
32
33    nums: tuple[str, ...]
34    """The 'num' page."""

Chars definitions for on-screen keyboard.

Category: App Classes

Keyboards are discoverable by the meta-tag system and the user can select which one they want to use. On-screen keyboard uses chars from active babase.Keyboard.

name: str

Displays when user selecting this keyboard.

chars: list[tuple[str, ...]]

Used for row/column lengths.

pages: dict[str, tuple[str, ...]]

Extra chars like emojis.

nums: tuple[str, ...]

The 'num' page.

class LoginAdapter:
 31class LoginAdapter:
 32    """Allows using implicit login types in an explicit way.
 33
 34    Some login types such as Google Play Game Services or Game Center are
 35    basically always present and often do not provide a way to log out
 36    from within a running app, so this adapter exists to use them in a
 37    flexible manner by 'attaching' and 'detaching' from an always-present
 38    login, allowing for its use alongside other login types. It also
 39    provides common functionality for server-side account verification and
 40    other handy bits.
 41    """
 42
 43    @dataclass
 44    class SignInResult:
 45        """Describes the final result of a sign-in attempt."""
 46
 47        credentials: str
 48
 49    @dataclass
 50    class ImplicitLoginState:
 51        """Describes the current state of an implicit login."""
 52
 53        login_id: str
 54        display_name: str
 55
 56    def __init__(self, login_type: LoginType):
 57        assert _babase.in_logic_thread()
 58        self.login_type = login_type
 59        self._implicit_login_state: LoginAdapter.ImplicitLoginState | None = (
 60            None
 61        )
 62        self._on_app_loading_called = False
 63        self._implicit_login_state_dirty = False
 64        self._back_end_active = False
 65
 66        # Which login of our type (if any) is associated with the
 67        # current active primary account.
 68        self._active_login_id: str | None = None
 69
 70        self._last_sign_in_time: float | None = None
 71        self._last_sign_in_desc: str | None = None
 72
 73    def on_app_loading(self) -> None:
 74        """Should be called for each adapter in on_app_loading."""
 75
 76        assert not self._on_app_loading_called
 77        self._on_app_loading_called = True
 78
 79        # Any implicit state we received up until now needs to be pushed
 80        # to the app account subsystem.
 81        self._update_implicit_login_state()
 82
 83    def set_implicit_login_state(
 84        self, state: ImplicitLoginState | None
 85    ) -> None:
 86        """Keep the adapter informed of implicit login states.
 87
 88        This should be called by the adapter back-end when an account
 89        of their associated type gets logged in or out.
 90        """
 91        assert _babase.in_logic_thread()
 92
 93        # Ignore redundant sets.
 94        if state == self._implicit_login_state:
 95            return
 96
 97        if state is None:
 98            logger.debug(
 99                '%s implicit state changed; now signed out.',
100                self.login_type.name,
101            )
102        else:
103            logger.debug(
104                '%s implicit state changed; now signed in as %s.',
105                self.login_type.name,
106                state.display_name,
107            )
108
109        self._implicit_login_state = state
110        self._implicit_login_state_dirty = True
111
112        # (possibly) push it to the app for handling.
113        self._update_implicit_login_state()
114
115        # This might affect whether we consider that back-end as 'active'.
116        self._update_back_end_active()
117
118    def set_active_logins(self, logins: dict[LoginType, str]) -> None:
119        """Keep the adapter informed of actively used logins.
120
121        This should be called by the app's account subsystem to
122        keep adapters up to date on the full set of logins attached
123        to the currently-in-use account.
124        Note that the logins dict passed in should be immutable as
125        only a reference to it is stored, not a copy.
126        """
127        assert _babase.in_logic_thread()
128        logger.debug(
129            '%s adapter got active logins %s.',
130            self.login_type.name,
131            {k: v[:4] + '...' + v[-4:] for k, v in logins.items()},
132        )
133
134        self._active_login_id = logins.get(self.login_type)
135        self._update_back_end_active()
136
137    def on_back_end_active_change(self, active: bool) -> None:
138        """Called when active state for the back-end is (possibly) changing.
139
140        Meant to be overridden by subclasses.
141        Being active means that the implicit login provided by the back-end
142        is actually being used by the app. It should therefore register
143        unlocked achievements, leaderboard scores, allow viewing native
144        UIs, etc. When not active it should ignore everything and behave
145        as if signed out, even if it technically is still signed in.
146        """
147        assert _babase.in_logic_thread()
148        del active  # Unused.
149
150    @final
151    def sign_in(
152        self,
153        result_cb: Callable[[LoginAdapter, SignInResult | Exception], None],
154        description: str,
155    ) -> None:
156        """Attempt to sign in via this adapter.
157
158        This can be called even if the back-end is not implicitly signed in;
159        the adapter will attempt to sign in if possible. An exception will
160        be returned if the sign-in attempt fails.
161        """
162
163        assert _babase.in_logic_thread()
164
165        # Have been seeing multiple sign-in attempts come through
166        # nearly simultaneously which can be problematic server-side.
167        # Let's error if a sign-in attempt is made within a few seconds
168        # of the last one to try and address this.
169        now = time.monotonic()
170        appnow = _babase.apptime()
171        if self._last_sign_in_time is not None:
172            since_last = now - self._last_sign_in_time
173            if since_last < 1.0:
174                logging.warning(
175                    'LoginAdapter: %s adapter sign_in() called too soon'
176                    ' (%.2fs) after last; this-desc="%s", last-desc="%s",'
177                    ' ba-app-time=%.2f.',
178                    self.login_type.name,
179                    since_last,
180                    description,
181                    self._last_sign_in_desc,
182                    appnow,
183                )
184                _babase.pushcall(
185                    partial(
186                        result_cb,
187                        self,
188                        RuntimeError('sign_in called too soon after last.'),
189                    )
190                )
191                return
192
193        self._last_sign_in_desc = description
194        self._last_sign_in_time = now
195
196        logger.debug(
197            '%s adapter sign_in() called; fetching sign-in-token...',
198            self.login_type.name,
199        )
200
201        def _got_sign_in_token_result(result: str | None) -> None:
202            import bacommon.cloud
203
204            # Failed to get a sign-in-token.
205            if result is None:
206                logger.debug(
207                    '%s adapter sign-in-token fetch failed;'
208                    ' aborting sign-in.',
209                    self.login_type.name,
210                )
211                _babase.pushcall(
212                    partial(
213                        result_cb,
214                        self,
215                        RuntimeError('fetch-sign-in-token failed.'),
216                    )
217                )
218                return
219
220            # Got a sign-in token! Now pass it to the cloud which will use
221            # it to verify our identity and give us app credentials on
222            # success.
223            logger.debug(
224                '%s adapter sign-in-token fetch succeeded;'
225                ' passing to cloud for verification...',
226                self.login_type.name,
227            )
228
229            def _got_sign_in_response(
230                response: bacommon.cloud.SignInResponse | Exception,
231            ) -> None:
232                # This likely means we couldn't communicate with the server.
233                if isinstance(response, Exception):
234                    logger.debug(
235                        '%s adapter got error sign-in response: %s',
236                        self.login_type.name,
237                        response,
238                    )
239                    _babase.pushcall(partial(result_cb, self, response))
240                else:
241                    # This means our credentials were explicitly rejected.
242                    if response.credentials is None:
243                        result2: LoginAdapter.SignInResult | Exception = (
244                            RuntimeError('Sign-in-token was rejected.')
245                        )
246                    else:
247                        logger.debug(
248                            '%s adapter got successful sign-in response',
249                            self.login_type.name,
250                        )
251                        result2 = self.SignInResult(
252                            credentials=response.credentials
253                        )
254                    _babase.pushcall(partial(result_cb, self, result2))
255
256            assert _babase.app.plus is not None
257            _babase.app.plus.cloud.send_message_cb(
258                bacommon.cloud.SignInMessage(
259                    self.login_type,
260                    result,
261                    description=description,
262                    apptime=appnow,
263                ),
264                on_response=_got_sign_in_response,
265            )
266
267        # Kick off the sign-in process by fetching a sign-in token.
268        self.get_sign_in_token(completion_cb=_got_sign_in_token_result)
269
270    def is_back_end_active(self) -> bool:
271        """Is this adapter's back-end currently active?"""
272        return self._back_end_active
273
274    def get_sign_in_token(
275        self, completion_cb: Callable[[str | None], None]
276    ) -> None:
277        """Get a sign-in token from the adapter back end.
278
279        This token is then passed to the master-server to complete the
280        sign-in process. The adapter can use this opportunity to bring
281        up account creation UI, call its internal sign_in function, etc.
282        as needed. The provided completion_cb should then be called with
283        either a token or None if sign in failed or was cancelled.
284        """
285
286        # Default implementation simply fails immediately.
287        _babase.pushcall(partial(completion_cb, None))
288
289    def _update_implicit_login_state(self) -> None:
290        # If we've received an implicit login state, schedule it to be
291        # sent along to the app. We wait until on-app-loading has been
292        # called so that account-client-v2 has had a chance to load
293        # any existing state so it can properly respond to this.
294        if self._implicit_login_state_dirty and self._on_app_loading_called:
295
296            logger.debug(
297                '%s adapter sending implicit-state-changed to app.',
298                self.login_type.name,
299            )
300
301            assert _babase.app.plus is not None
302            _babase.pushcall(
303                partial(
304                    _babase.app.plus.accounts.on_implicit_login_state_changed,
305                    self.login_type,
306                    self._implicit_login_state,
307                )
308            )
309            self._implicit_login_state_dirty = False
310
311    def _update_back_end_active(self) -> None:
312        was_active = self._back_end_active
313        if self._implicit_login_state is None:
314            is_active = False
315        else:
316            is_active = (
317                self._implicit_login_state.login_id == self._active_login_id
318            )
319        if was_active != is_active:
320            logger.debug(
321                '%s adapter back-end-active is now %s.',
322                self.login_type.name,
323                is_active,
324            )
325            self.on_back_end_active_change(is_active)
326            self._back_end_active = is_active

Allows using implicit login types in an explicit way.

Some login types such as Google Play Game Services or Game Center are basically always present and often do not provide a way to log out from within a running app, so this adapter exists to use them in a flexible manner by 'attaching' and 'detaching' from an always-present login, allowing for its use alongside other login types. It also provides common functionality for server-side account verification and other handy bits.

LoginAdapter(login_type: bacommon.login.LoginType)
56    def __init__(self, login_type: LoginType):
57        assert _babase.in_logic_thread()
58        self.login_type = login_type
59        self._implicit_login_state: LoginAdapter.ImplicitLoginState | None = (
60            None
61        )
62        self._on_app_loading_called = False
63        self._implicit_login_state_dirty = False
64        self._back_end_active = False
65
66        # Which login of our type (if any) is associated with the
67        # current active primary account.
68        self._active_login_id: str | None = None
69
70        self._last_sign_in_time: float | None = None
71        self._last_sign_in_desc: str | None = None
login_type
def on_app_loading(self) -> None:
73    def on_app_loading(self) -> None:
74        """Should be called for each adapter in on_app_loading."""
75
76        assert not self._on_app_loading_called
77        self._on_app_loading_called = True
78
79        # Any implicit state we received up until now needs to be pushed
80        # to the app account subsystem.
81        self._update_implicit_login_state()

Should be called for each adapter in on_app_loading.

def set_implicit_login_state( self, state: LoginAdapter.ImplicitLoginState | None) -> None:
 83    def set_implicit_login_state(
 84        self, state: ImplicitLoginState | None
 85    ) -> None:
 86        """Keep the adapter informed of implicit login states.
 87
 88        This should be called by the adapter back-end when an account
 89        of their associated type gets logged in or out.
 90        """
 91        assert _babase.in_logic_thread()
 92
 93        # Ignore redundant sets.
 94        if state == self._implicit_login_state:
 95            return
 96
 97        if state is None:
 98            logger.debug(
 99                '%s implicit state changed; now signed out.',
100                self.login_type.name,
101            )
102        else:
103            logger.debug(
104                '%s implicit state changed; now signed in as %s.',
105                self.login_type.name,
106                state.display_name,
107            )
108
109        self._implicit_login_state = state
110        self._implicit_login_state_dirty = True
111
112        # (possibly) push it to the app for handling.
113        self._update_implicit_login_state()
114
115        # This might affect whether we consider that back-end as 'active'.
116        self._update_back_end_active()

Keep the adapter informed of implicit login states.

This should be called by the adapter back-end when an account of their associated type gets logged in or out.

def set_active_logins(self, logins: dict[bacommon.login.LoginType, str]) -> None:
118    def set_active_logins(self, logins: dict[LoginType, str]) -> None:
119        """Keep the adapter informed of actively used logins.
120
121        This should be called by the app's account subsystem to
122        keep adapters up to date on the full set of logins attached
123        to the currently-in-use account.
124        Note that the logins dict passed in should be immutable as
125        only a reference to it is stored, not a copy.
126        """
127        assert _babase.in_logic_thread()
128        logger.debug(
129            '%s adapter got active logins %s.',
130            self.login_type.name,
131            {k: v[:4] + '...' + v[-4:] for k, v in logins.items()},
132        )
133
134        self._active_login_id = logins.get(self.login_type)
135        self._update_back_end_active()

Keep the adapter informed of actively used logins.

This should be called by the app's account subsystem to keep adapters up to date on the full set of logins attached to the currently-in-use account. Note that the logins dict passed in should be immutable as only a reference to it is stored, not a copy.

def on_back_end_active_change(self, active: bool) -> None:
137    def on_back_end_active_change(self, active: bool) -> None:
138        """Called when active state for the back-end is (possibly) changing.
139
140        Meant to be overridden by subclasses.
141        Being active means that the implicit login provided by the back-end
142        is actually being used by the app. It should therefore register
143        unlocked achievements, leaderboard scores, allow viewing native
144        UIs, etc. When not active it should ignore everything and behave
145        as if signed out, even if it technically is still signed in.
146        """
147        assert _babase.in_logic_thread()
148        del active  # Unused.

Called when active state for the back-end is (possibly) changing.

Meant to be overridden by subclasses. Being active means that the implicit login provided by the back-end is actually being used by the app. It should therefore register unlocked achievements, leaderboard scores, allow viewing native UIs, etc. When not active it should ignore everything and behave as if signed out, even if it technically is still signed in.

@final
def sign_in( self, result_cb: Callable[[LoginAdapter, LoginAdapter.SignInResult | Exception], NoneType], description: str) -> None:
150    @final
151    def sign_in(
152        self,
153        result_cb: Callable[[LoginAdapter, SignInResult | Exception], None],
154        description: str,
155    ) -> None:
156        """Attempt to sign in via this adapter.
157
158        This can be called even if the back-end is not implicitly signed in;
159        the adapter will attempt to sign in if possible. An exception will
160        be returned if the sign-in attempt fails.
161        """
162
163        assert _babase.in_logic_thread()
164
165        # Have been seeing multiple sign-in attempts come through
166        # nearly simultaneously which can be problematic server-side.
167        # Let's error if a sign-in attempt is made within a few seconds
168        # of the last one to try and address this.
169        now = time.monotonic()
170        appnow = _babase.apptime()
171        if self._last_sign_in_time is not None:
172            since_last = now - self._last_sign_in_time
173            if since_last < 1.0:
174                logging.warning(
175                    'LoginAdapter: %s adapter sign_in() called too soon'
176                    ' (%.2fs) after last; this-desc="%s", last-desc="%s",'
177                    ' ba-app-time=%.2f.',
178                    self.login_type.name,
179                    since_last,
180                    description,
181                    self._last_sign_in_desc,
182                    appnow,
183                )
184                _babase.pushcall(
185                    partial(
186                        result_cb,
187                        self,
188                        RuntimeError('sign_in called too soon after last.'),
189                    )
190                )
191                return
192
193        self._last_sign_in_desc = description
194        self._last_sign_in_time = now
195
196        logger.debug(
197            '%s adapter sign_in() called; fetching sign-in-token...',
198            self.login_type.name,
199        )
200
201        def _got_sign_in_token_result(result: str | None) -> None:
202            import bacommon.cloud
203
204            # Failed to get a sign-in-token.
205            if result is None:
206                logger.debug(
207                    '%s adapter sign-in-token fetch failed;'
208                    ' aborting sign-in.',
209                    self.login_type.name,
210                )
211                _babase.pushcall(
212                    partial(
213                        result_cb,
214                        self,
215                        RuntimeError('fetch-sign-in-token failed.'),
216                    )
217                )
218                return
219
220            # Got a sign-in token! Now pass it to the cloud which will use
221            # it to verify our identity and give us app credentials on
222            # success.
223            logger.debug(
224                '%s adapter sign-in-token fetch succeeded;'
225                ' passing to cloud for verification...',
226                self.login_type.name,
227            )
228
229            def _got_sign_in_response(
230                response: bacommon.cloud.SignInResponse | Exception,
231            ) -> None:
232                # This likely means we couldn't communicate with the server.
233                if isinstance(response, Exception):
234                    logger.debug(
235                        '%s adapter got error sign-in response: %s',
236                        self.login_type.name,
237                        response,
238                    )
239                    _babase.pushcall(partial(result_cb, self, response))
240                else:
241                    # This means our credentials were explicitly rejected.
242                    if response.credentials is None:
243                        result2: LoginAdapter.SignInResult | Exception = (
244                            RuntimeError('Sign-in-token was rejected.')
245                        )
246                    else:
247                        logger.debug(
248                            '%s adapter got successful sign-in response',
249                            self.login_type.name,
250                        )
251                        result2 = self.SignInResult(
252                            credentials=response.credentials
253                        )
254                    _babase.pushcall(partial(result_cb, self, result2))
255
256            assert _babase.app.plus is not None
257            _babase.app.plus.cloud.send_message_cb(
258                bacommon.cloud.SignInMessage(
259                    self.login_type,
260                    result,
261                    description=description,
262                    apptime=appnow,
263                ),
264                on_response=_got_sign_in_response,
265            )
266
267        # Kick off the sign-in process by fetching a sign-in token.
268        self.get_sign_in_token(completion_cb=_got_sign_in_token_result)

Attempt to sign in via this adapter.

This can be called even if the back-end is not implicitly signed in; the adapter will attempt to sign in if possible. An exception will be returned if the sign-in attempt fails.

def is_back_end_active(self) -> bool:
270    def is_back_end_active(self) -> bool:
271        """Is this adapter's back-end currently active?"""
272        return self._back_end_active

Is this adapter's back-end currently active?

def get_sign_in_token(self, completion_cb: Callable[[str | None], NoneType]) -> None:
274    def get_sign_in_token(
275        self, completion_cb: Callable[[str | None], None]
276    ) -> None:
277        """Get a sign-in token from the adapter back end.
278
279        This token is then passed to the master-server to complete the
280        sign-in process. The adapter can use this opportunity to bring
281        up account creation UI, call its internal sign_in function, etc.
282        as needed. The provided completion_cb should then be called with
283        either a token or None if sign in failed or was cancelled.
284        """
285
286        # Default implementation simply fails immediately.
287        _babase.pushcall(partial(completion_cb, None))

Get a sign-in token from the adapter back end.

This token is then passed to the master-server to complete the sign-in process. The adapter can use this opportunity to bring up account creation UI, call its internal sign_in function, etc. as needed. The provided completion_cb should then be called with either a token or None if sign in failed or was cancelled.

@dataclass
class LoginAdapter.SignInResult:
43    @dataclass
44    class SignInResult:
45        """Describes the final result of a sign-in attempt."""
46
47        credentials: str

Describes the final result of a sign-in attempt.

LoginAdapter.SignInResult(credentials: str)
credentials: str
@dataclass
class LoginAdapter.ImplicitLoginState:
49    @dataclass
50    class ImplicitLoginState:
51        """Describes the current state of an implicit login."""
52
53        login_id: str
54        display_name: str

Describes the current state of an implicit login.

LoginAdapter.ImplicitLoginState(login_id: str, display_name: str)
login_id: str
display_name: str
@dataclass
class LoginInfo:
24@dataclass
25class LoginInfo:
26    """Basic info about a login available in the app.plus.accounts section."""
27
28    name: str

Basic info about a login available in the app.plus.accounts section.

LoginInfo(name: str)
name: str
class Lstr:
496class Lstr:
497    """Used to define strings in a language-independent way.
498
499    Category: **General Utility Classes**
500
501    These should be used whenever possible in place of hard-coded
502    strings so that in-game or UI elements show up correctly on all
503    clients in their currently-active language.
504
505    To see available resource keys, look at any of the bs_language_*.py
506    files in the game or the translations pages at
507    legacy.ballistica.net/translate.
508
509    ##### Examples
510    EXAMPLE 1: specify a string from a resource path
511    >>> mynode.text = babase.Lstr(resource='audioSettingsWindow.titleText')
512
513    EXAMPLE 2: specify a translated string via a category and english
514    value; if a translated value is available, it will be used; otherwise
515    the english value will be. To see available translation categories,
516    look under the 'translations' resource section.
517    >>> mynode.text = babase.Lstr(translate=('gameDescriptions',
518    ...                                  'Defeat all enemies'))
519
520    EXAMPLE 3: specify a raw value and some substitutions. Substitutions
521    can be used with resource and translate modes as well.
522    >>> mynode.text = babase.Lstr(value='${A} / ${B}',
523    ...               subs=[('${A}', str(score)), ('${B}', str(total))])
524
525    EXAMPLE 4: babase.Lstr's can be nested. This example would display the
526    resource at res_a but replace ${NAME} with the value of the
527    resource at res_b
528    >>> mytextnode.text = babase.Lstr(
529    ...     resource='res_a',
530    ...     subs=[('${NAME}', babase.Lstr(resource='res_b'))])
531    """
532
533    # This class is used a lot in UI stuff and doesn't need to be
534    # flexible, so let's optimize its performance a bit.
535    __slots__ = ['args']
536
537    @overload
538    def __init__(
539        self,
540        *,
541        resource: str,
542        fallback_resource: str = '',
543        fallback_value: str = '',
544        subs: Sequence[tuple[str, str | Lstr]] | None = None,
545    ) -> None:
546        """Create an Lstr from a string resource."""
547
548    @overload
549    def __init__(
550        self,
551        *,
552        translate: tuple[str, str],
553        subs: Sequence[tuple[str, str | Lstr]] | None = None,
554    ) -> None:
555        """Create an Lstr by translating a string in a category."""
556
557    @overload
558    def __init__(
559        self,
560        *,
561        value: str,
562        subs: Sequence[tuple[str, str | Lstr]] | None = None,
563    ) -> None:
564        """Create an Lstr from a raw string value."""
565
566    def __init__(self, *args: Any, **keywds: Any) -> None:
567        """Instantiate a Lstr.
568
569        Pass a value for either 'resource', 'translate',
570        or 'value'. (see Lstr help for examples).
571        'subs' can be a sequence of 2-member sequences consisting of values
572        and replacements.
573        'fallback_resource' can be a resource key that will be used if the
574        main one is not present for
575        the current language in place of falling back to the english value
576        ('resource' mode only).
577        'fallback_value' can be a literal string that will be used if neither
578        the resource nor the fallback resource is found ('resource' mode only).
579        """
580        # pylint: disable=too-many-branches
581        if args:
582            raise TypeError('Lstr accepts only keyword arguments')
583
584        # Basically just store the exact args they passed.
585        # However if they passed any Lstr values for subs,
586        # replace them with that Lstr's dict.
587        self.args = keywds
588        our_type = type(self)
589
590        if isinstance(self.args.get('value'), our_type):
591            raise TypeError("'value' must be a regular string; not an Lstr")
592
593        if 'subs' in keywds:
594            subs = keywds.get('subs')
595            subs_filtered = []
596            if subs is not None:
597                for key, value in keywds['subs']:
598                    if isinstance(value, our_type):
599                        subs_filtered.append((key, value.args))
600                    else:
601                        subs_filtered.append((key, value))
602            self.args['subs'] = subs_filtered
603
604        # As of protocol 31 we support compact key names
605        # ('t' instead of 'translate', etc). Convert as needed.
606        if 'translate' in keywds:
607            keywds['t'] = keywds['translate']
608            del keywds['translate']
609        if 'resource' in keywds:
610            keywds['r'] = keywds['resource']
611            del keywds['resource']
612        if 'value' in keywds:
613            keywds['v'] = keywds['value']
614            del keywds['value']
615        if 'fallback' in keywds:
616            from babase import _error
617
618            _error.print_error(
619                'deprecated "fallback" arg passed to Lstr(); use '
620                'either "fallback_resource" or "fallback_value"',
621                once=True,
622            )
623            keywds['f'] = keywds['fallback']
624            del keywds['fallback']
625        if 'fallback_resource' in keywds:
626            keywds['f'] = keywds['fallback_resource']
627            del keywds['fallback_resource']
628        if 'subs' in keywds:
629            keywds['s'] = keywds['subs']
630            del keywds['subs']
631        if 'fallback_value' in keywds:
632            keywds['fv'] = keywds['fallback_value']
633            del keywds['fallback_value']
634
635    def evaluate(self) -> str:
636        """Evaluate the Lstr and returns a flat string in the current language.
637
638        You should avoid doing this as much as possible and instead pass
639        and store Lstr values.
640        """
641        return _babase.evaluate_lstr(self._get_json())
642
643    def is_flat_value(self) -> bool:
644        """Return whether the Lstr is a 'flat' value.
645
646        This is defined as a simple string value incorporating no
647        translations, resources, or substitutions. In this case it may
648        be reasonable to replace it with a raw string value, perform
649        string manipulation on it, etc.
650        """
651        return bool('v' in self.args and not self.args.get('s', []))
652
653    def _get_json(self) -> str:
654        try:
655            return json.dumps(self.args, separators=(',', ':'))
656        except Exception:
657            from babase import _error
658
659            _error.print_exception('_get_json failed for', self.args)
660            return 'JSON_ERR'
661
662    @override
663    def __str__(self) -> str:
664        return '<ba.Lstr: ' + self._get_json() + '>'
665
666    @override
667    def __repr__(self) -> str:
668        return '<ba.Lstr: ' + self._get_json() + '>'
669
670    @staticmethod
671    def from_json(json_string: str) -> babase.Lstr:
672        """Given a json string, returns a babase.Lstr. Does no validation."""
673        lstr = Lstr(value='')
674        lstr.args = json.loads(json_string)
675        return lstr

Used to define strings in a language-independent way.

Category: General Utility Classes

These should be used whenever possible in place of hard-coded strings so that in-game or UI elements show up correctly on all clients in their currently-active language.

To see available resource keys, look at any of the bs_language_*.py files in the game or the translations pages at legacy.ballistica.net/translate.

Examples

EXAMPLE 1: specify a string from a resource path

>>> mynode.text = babase.Lstr(resource='audioSettingsWindow.titleText')

EXAMPLE 2: specify a translated string via a category and english value; if a translated value is available, it will be used; otherwise the english value will be. To see available translation categories, look under the 'translations' resource section.

>>> mynode.text = babase.Lstr(translate=('gameDescriptions',
...                                  'Defeat all enemies'))

EXAMPLE 3: specify a raw value and some substitutions. Substitutions can be used with resource and translate modes as well.

>>> mynode.text = babase.Lstr(value='${A} / ${B}',
...               subs=[('${A}', str(score)), ('${B}', str(total))])

EXAMPLE 4: babase.Lstr's can be nested. This example would display the resource at res_a but replace ${NAME} with the value of the resource at res_b

>>> mytextnode.text = babase.Lstr(
...     resource='res_a',
...     subs=[('${NAME}', babase.Lstr(resource='res_b'))])
Lstr(*args: Any, **keywds: Any)
566    def __init__(self, *args: Any, **keywds: Any) -> None:
567        """Instantiate a Lstr.
568
569        Pass a value for either 'resource', 'translate',
570        or 'value'. (see Lstr help for examples).
571        'subs' can be a sequence of 2-member sequences consisting of values
572        and replacements.
573        'fallback_resource' can be a resource key that will be used if the
574        main one is not present for
575        the current language in place of falling back to the english value
576        ('resource' mode only).
577        'fallback_value' can be a literal string that will be used if neither
578        the resource nor the fallback resource is found ('resource' mode only).
579        """
580        # pylint: disable=too-many-branches
581        if args:
582            raise TypeError('Lstr accepts only keyword arguments')
583
584        # Basically just store the exact args they passed.
585        # However if they passed any Lstr values for subs,
586        # replace them with that Lstr's dict.
587        self.args = keywds
588        our_type = type(self)
589
590        if isinstance(self.args.get('value'), our_type):
591            raise TypeError("'value' must be a regular string; not an Lstr")
592
593        if 'subs' in keywds:
594            subs = keywds.get('subs')
595            subs_filtered = []
596            if subs is not None:
597                for key, value in keywds['subs']:
598                    if isinstance(value, our_type):
599                        subs_filtered.append((key, value.args))
600                    else:
601                        subs_filtered.append((key, value))
602            self.args['subs'] = subs_filtered
603
604        # As of protocol 31 we support compact key names
605        # ('t' instead of 'translate', etc). Convert as needed.
606        if 'translate' in keywds:
607            keywds['t'] = keywds['translate']
608            del keywds['translate']
609        if 'resource' in keywds:
610            keywds['r'] = keywds['resource']
611            del keywds['resource']
612        if 'value' in keywds:
613            keywds['v'] = keywds['value']
614            del keywds['value']
615        if 'fallback' in keywds:
616            from babase import _error
617
618            _error.print_error(
619                'deprecated "fallback" arg passed to Lstr(); use '
620                'either "fallback_resource" or "fallback_value"',
621                once=True,
622            )
623            keywds['f'] = keywds['fallback']
624            del keywds['fallback']
625        if 'fallback_resource' in keywds:
626            keywds['f'] = keywds['fallback_resource']
627            del keywds['fallback_resource']
628        if 'subs' in keywds:
629            keywds['s'] = keywds['subs']
630            del keywds['subs']
631        if 'fallback_value' in keywds:
632            keywds['fv'] = keywds['fallback_value']
633            del keywds['fallback_value']

Instantiate a Lstr.

Pass a value for either 'resource', 'translate', or 'value'. (see Lstr help for examples). 'subs' can be a sequence of 2-member sequences consisting of values and replacements. 'fallback_resource' can be a resource key that will be used if the main one is not present for the current language in place of falling back to the english value ('resource' mode only). 'fallback_value' can be a literal string that will be used if neither the resource nor the fallback resource is found ('resource' mode only).

args
def evaluate(self) -> str:
635    def evaluate(self) -> str:
636        """Evaluate the Lstr and returns a flat string in the current language.
637
638        You should avoid doing this as much as possible and instead pass
639        and store Lstr values.
640        """
641        return _babase.evaluate_lstr(self._get_json())

Evaluate the Lstr and returns a flat string in the current language.

You should avoid doing this as much as possible and instead pass and store Lstr values.

def is_flat_value(self) -> bool:
643    def is_flat_value(self) -> bool:
644        """Return whether the Lstr is a 'flat' value.
645
646        This is defined as a simple string value incorporating no
647        translations, resources, or substitutions. In this case it may
648        be reasonable to replace it with a raw string value, perform
649        string manipulation on it, etc.
650        """
651        return bool('v' in self.args and not self.args.get('s', []))

Return whether the Lstr is a 'flat' value.

This is defined as a simple string value incorporating no translations, resources, or substitutions. In this case it may be reasonable to replace it with a raw string value, perform string manipulation on it, etc.

@staticmethod
def from_json(json_string: str) -> Lstr:
670    @staticmethod
671    def from_json(json_string: str) -> babase.Lstr:
672        """Given a json string, returns a babase.Lstr. Does no validation."""
673        lstr = Lstr(value='')
674        lstr.args = json.loads(json_string)
675        return lstr

Given a json string, returns a babase.Lstr. Does no validation.

class MainWindow(bauiv1.Window):
 49class MainWindow(Window):
 50    """A special type of window that can be set as 'main'.
 51
 52    The UI system has at most one main window at any given time.
 53    MainWindows support high level functionality such as saving and
 54    restoring states, allowing them to be automatically recreated when
 55    navigating back from other locations or when something like ui-scale
 56    changes.
 57    """
 58
 59    def __init__(
 60        self,
 61        root_widget: bauiv1.Widget,
 62        *,
 63        transition: str | None,
 64        origin_widget: bauiv1.Widget | None,
 65        cleanupcheck: bool = True,
 66        refresh_on_screen_size_changes: bool = False,
 67    ):
 68        """Create a MainWindow given a root widget and transition info.
 69
 70        Automatically handles in and out transitions on the provided widget,
 71        so there is no need to set transitions when creating it.
 72        """
 73        # A back-state supplied by the ui system.
 74        self.main_window_back_state: MainWindowState | None = None
 75
 76        self.main_window_is_top_level: bool = False
 77
 78        # Windows that size tailor themselves to exact screen dimensions
 79        # can pass True for this. Generally this only applies to small
 80        # ui scale and at larger scales windows simply fit in the
 81        # virtual safe area.
 82        self.refreshes_on_screen_size_changes = refresh_on_screen_size_changes
 83
 84        # Windows can be flagged as auxiliary when not related to the
 85        # main UI task at hand. UI code may choose to handle auxiliary
 86        # windows in special ways, such as by implicitly replacing
 87        # existing auxiliary windows with new ones instead of keeping
 88        # old ones as back targets.
 89        self.main_window_is_auxiliary: bool = False
 90
 91        self._main_window_transition = transition
 92        self._main_window_origin_widget = origin_widget
 93        super().__init__(root_widget, cleanupcheck)
 94
 95        scale_origin: tuple[float, float] | None
 96        if origin_widget is not None:
 97            self._main_window_transition_out = 'out_scale'
 98            scale_origin = origin_widget.get_screen_space_center()
 99            transition = 'in_scale'
100        else:
101            self._main_window_transition_out = 'out_right'
102            scale_origin = None
103        _bauiv1.containerwidget(
104            edit=root_widget,
105            transition=transition,
106            scale_origin_stack_offset=scale_origin,
107        )
108
109    def main_window_close(self, transition: str | None = None) -> None:
110        """Get window transitioning out if still alive."""
111
112        # no-op if our underlying widget is dead or on its way out.
113        if not self._root_widget or self._root_widget.transitioning_out:
114            return
115
116        # Transition ourself out.
117        try:
118            self.on_main_window_close()
119        except Exception:
120            logging.exception('Error in on_main_window_close() for %s.', self)
121
122        # Note: normally transition of None means instant, but we use
123        # that to mean 'do the default' so we support a special
124        # 'instant' string..
125        if transition == 'instant':
126            self._root_widget.delete()
127        else:
128            _bauiv1.containerwidget(
129                edit=self._root_widget,
130                transition=(
131                    self._main_window_transition_out
132                    if transition is None
133                    else transition
134                ),
135            )
136
137    def main_window_has_control(self) -> bool:
138        """Is this MainWindow allowed to change the global main window?
139
140        It is a good idea to make sure this is True before calling
141        main_window_replace(). This prevents fluke UI breakage such as
142        multiple simultaneous events causing a MainWindow to spawn
143        multiple replacements for itself.
144        """
145        # We are allowed to change main windows if we are the current one
146        # AND our underlying widget is still alive and not transitioning out.
147        return (
148            babase.app.ui_v1.get_main_window() is self
149            and bool(self._root_widget)
150            and not self._root_widget.transitioning_out
151        )
152
153    def main_window_back(self) -> None:
154        """Move back in the main window stack.
155
156        Is a no-op if the main window does not have control;
157        no need to check main_window_has_control() first.
158        """
159
160        # Users should always check main_window_has_control() before
161        # calling us. Error if it seems they did not.
162        if not self.main_window_has_control():
163            return
164
165        uiv1 = babase.app.ui_v1
166
167        # Get the 'back' window coming in.
168        if not self.main_window_is_top_level:
169
170            back_state = self.main_window_back_state
171            if back_state is None:
172                raise RuntimeError(
173                    f'Main window {self} provides no back-state.'
174                )
175
176            # Valid states should have values here.
177            assert back_state.is_top_level is not None
178            assert back_state.is_auxiliary is not None
179            assert back_state.window_type is not None
180
181            backwin = back_state.create_window(transition='in_left')
182
183            uiv1.set_main_window(
184                backwin,
185                from_window=self,
186                is_back=True,
187                back_state=back_state,
188                suppress_warning=True,
189            )
190
191        # Transition ourself out.
192        self.main_window_close()
193
194    def main_window_replace(
195        self,
196        new_window: MainWindow,
197        back_state: MainWindowState | None = None,
198        is_auxiliary: bool = False,
199    ) -> None:
200        """Replace ourself with a new MainWindow."""
201
202        # Users should always check main_window_has_control() *before*
203        # creating new MainWindows and passing them in here. Kill the
204        # passed window and Error if it seems they did not.
205        if not self.main_window_has_control():
206            new_window.get_root_widget().delete()
207            raise RuntimeError(
208                f'main_window_replace() called on a not-in-control window'
209                f' ({self}); always check main_window_has_control() before'
210                f' calling main_window_replace().'
211            )
212
213        # Just shove the old out the left to give the feel that we're
214        # adding to the nav stack.
215        transition = 'out_left'
216
217        # Transition ourself out.
218        try:
219            self.on_main_window_close()
220        except Exception:
221            logging.exception('Error in on_main_window_close() for %s.', self)
222
223        _bauiv1.containerwidget(edit=self._root_widget, transition=transition)
224        babase.app.ui_v1.set_main_window(
225            new_window,
226            from_window=self,
227            back_state=back_state,
228            is_auxiliary=is_auxiliary,
229            suppress_warning=True,
230        )
231
232    def on_main_window_close(self) -> None:
233        """Called before transitioning out a main window.
234
235        A good opportunity to save window state/etc.
236        """
237
238    def get_main_window_state(self) -> MainWindowState:
239        """Return a WindowState to recreate this window, if supported."""
240        raise NotImplementedError()

A special type of window that can be set as 'main'.

The UI system has at most one main window at any given time. MainWindows support high level functionality such as saving and restoring states, allowing them to be automatically recreated when navigating back from other locations or when something like ui-scale changes.

MainWindow( root_widget: _bauiv1.Widget, *, transition: str | None, origin_widget: _bauiv1.Widget | None, cleanupcheck: bool = True, refresh_on_screen_size_changes: bool = False)
 59    def __init__(
 60        self,
 61        root_widget: bauiv1.Widget,
 62        *,
 63        transition: str | None,
 64        origin_widget: bauiv1.Widget | None,
 65        cleanupcheck: bool = True,
 66        refresh_on_screen_size_changes: bool = False,
 67    ):
 68        """Create a MainWindow given a root widget and transition info.
 69
 70        Automatically handles in and out transitions on the provided widget,
 71        so there is no need to set transitions when creating it.
 72        """
 73        # A back-state supplied by the ui system.
 74        self.main_window_back_state: MainWindowState | None = None
 75
 76        self.main_window_is_top_level: bool = False
 77
 78        # Windows that size tailor themselves to exact screen dimensions
 79        # can pass True for this. Generally this only applies to small
 80        # ui scale and at larger scales windows simply fit in the
 81        # virtual safe area.
 82        self.refreshes_on_screen_size_changes = refresh_on_screen_size_changes
 83
 84        # Windows can be flagged as auxiliary when not related to the
 85        # main UI task at hand. UI code may choose to handle auxiliary
 86        # windows in special ways, such as by implicitly replacing
 87        # existing auxiliary windows with new ones instead of keeping
 88        # old ones as back targets.
 89        self.main_window_is_auxiliary: bool = False
 90
 91        self._main_window_transition = transition
 92        self._main_window_origin_widget = origin_widget
 93        super().__init__(root_widget, cleanupcheck)
 94
 95        scale_origin: tuple[float, float] | None
 96        if origin_widget is not None:
 97            self._main_window_transition_out = 'out_scale'
 98            scale_origin = origin_widget.get_screen_space_center()
 99            transition = 'in_scale'
100        else:
101            self._main_window_transition_out = 'out_right'
102            scale_origin = None
103        _bauiv1.containerwidget(
104            edit=root_widget,
105            transition=transition,
106            scale_origin_stack_offset=scale_origin,
107        )

Create a MainWindow given a root widget and transition info.

Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it.

main_window_back_state: MainWindowState | None
main_window_is_top_level: bool
refreshes_on_screen_size_changes
main_window_is_auxiliary: bool
def main_window_close(self, transition: str | None = None) -> None:
109    def main_window_close(self, transition: str | None = None) -> None:
110        """Get window transitioning out if still alive."""
111
112        # no-op if our underlying widget is dead or on its way out.
113        if not self._root_widget or self._root_widget.transitioning_out:
114            return
115
116        # Transition ourself out.
117        try:
118            self.on_main_window_close()
119        except Exception:
120            logging.exception('Error in on_main_window_close() for %s.', self)
121
122        # Note: normally transition of None means instant, but we use
123        # that to mean 'do the default' so we support a special
124        # 'instant' string..
125        if transition == 'instant':
126            self._root_widget.delete()
127        else:
128            _bauiv1.containerwidget(
129                edit=self._root_widget,
130                transition=(
131                    self._main_window_transition_out
132                    if transition is None
133                    else transition
134                ),
135            )

Get window transitioning out if still alive.

def main_window_has_control(self) -> bool:
137    def main_window_has_control(self) -> bool:
138        """Is this MainWindow allowed to change the global main window?
139
140        It is a good idea to make sure this is True before calling
141        main_window_replace(). This prevents fluke UI breakage such as
142        multiple simultaneous events causing a MainWindow to spawn
143        multiple replacements for itself.
144        """
145        # We are allowed to change main windows if we are the current one
146        # AND our underlying widget is still alive and not transitioning out.
147        return (
148            babase.app.ui_v1.get_main_window() is self
149            and bool(self._root_widget)
150            and not self._root_widget.transitioning_out
151        )

Is this MainWindow allowed to change the global main window?

It is a good idea to make sure this is True before calling main_window_replace(). This prevents fluke UI breakage such as multiple simultaneous events causing a MainWindow to spawn multiple replacements for itself.

def main_window_back(self) -> None:
153    def main_window_back(self) -> None:
154        """Move back in the main window stack.
155
156        Is a no-op if the main window does not have control;
157        no need to check main_window_has_control() first.
158        """
159
160        # Users should always check main_window_has_control() before
161        # calling us. Error if it seems they did not.
162        if not self.main_window_has_control():
163            return
164
165        uiv1 = babase.app.ui_v1
166
167        # Get the 'back' window coming in.
168        if not self.main_window_is_top_level:
169
170            back_state = self.main_window_back_state
171            if back_state is None:
172                raise RuntimeError(
173                    f'Main window {self} provides no back-state.'
174                )
175
176            # Valid states should have values here.
177            assert back_state.is_top_level is not None
178            assert back_state.is_auxiliary is not None
179            assert back_state.window_type is not None
180
181            backwin = back_state.create_window(transition='in_left')
182
183            uiv1.set_main_window(
184                backwin,
185                from_window=self,
186                is_back=True,
187                back_state=back_state,
188                suppress_warning=True,
189            )
190
191        # Transition ourself out.
192        self.main_window_close()

Move back in the main window stack.

Is a no-op if the main window does not have control; no need to check main_window_has_control() first.

def main_window_replace( self, new_window: MainWindow, back_state: MainWindowState | None = None, is_auxiliary: bool = False) -> None:
194    def main_window_replace(
195        self,
196        new_window: MainWindow,
197        back_state: MainWindowState | None = None,
198        is_auxiliary: bool = False,
199    ) -> None:
200        """Replace ourself with a new MainWindow."""
201
202        # Users should always check main_window_has_control() *before*
203        # creating new MainWindows and passing them in here. Kill the
204        # passed window and Error if it seems they did not.
205        if not self.main_window_has_control():
206            new_window.get_root_widget().delete()
207            raise RuntimeError(
208                f'main_window_replace() called on a not-in-control window'
209                f' ({self}); always check main_window_has_control() before'
210                f' calling main_window_replace().'
211            )
212
213        # Just shove the old out the left to give the feel that we're
214        # adding to the nav stack.
215        transition = 'out_left'
216
217        # Transition ourself out.
218        try:
219            self.on_main_window_close()
220        except Exception:
221            logging.exception('Error in on_main_window_close() for %s.', self)
222
223        _bauiv1.containerwidget(edit=self._root_widget, transition=transition)
224        babase.app.ui_v1.set_main_window(
225            new_window,
226            from_window=self,
227            back_state=back_state,
228            is_auxiliary=is_auxiliary,
229            suppress_warning=True,
230        )

Replace ourself with a new MainWindow.

def on_main_window_close(self) -> None:
232    def on_main_window_close(self) -> None:
233        """Called before transitioning out a main window.
234
235        A good opportunity to save window state/etc.
236        """

Called before transitioning out a main window.

A good opportunity to save window state/etc.

def get_main_window_state(self) -> MainWindowState:
238    def get_main_window_state(self) -> MainWindowState:
239        """Return a WindowState to recreate this window, if supported."""
240        raise NotImplementedError()

Return a WindowState to recreate this window, if supported.

class MainWindowState:
243class MainWindowState:
244    """Persistent state for a specific MainWindow.
245
246    This allows MainWindows to be automatically recreated for back-button
247    purposes, when switching app-modes, etc.
248    """
249
250    def __init__(self) -> None:
251        # The window that back/cancel navigation should take us to.
252        self.parent: MainWindowState | None = None
253        self.is_top_level: bool | None = None
254        self.is_auxiliary: bool | None = None
255        self.window_type: type[MainWindow] | None = None
256        self.selection: str | None = None
257
258    def create_window(
259        self,
260        transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
261        origin_widget: bauiv1.Widget | None = None,
262    ) -> MainWindow:
263        """Create a window based on this state.
264
265        WindowState child classes should override this to recreate their
266        particular type of window.
267        """
268        raise NotImplementedError()

Persistent state for a specific MainWindow.

This allows MainWindows to be automatically recreated for back-button purposes, when switching app-modes, etc.

parent: MainWindowState | None
is_top_level: bool | None
is_auxiliary: bool | None
window_type: type[MainWindow] | None
selection: str | None
def create_window( self, transition: Optional[Literal['in_right', 'in_left', 'in_scale']] = None, origin_widget: _bauiv1.Widget | None = None) -> MainWindow:
258    def create_window(
259        self,
260        transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
261        origin_widget: bauiv1.Widget | None = None,
262    ) -> MainWindow:
263        """Create a window based on this state.
264
265        WindowState child classes should override this to recreate their
266        particular type of window.
267        """
268        raise NotImplementedError()

Create a window based on this state.

WindowState child classes should override this to recreate their particular type of window.

class Mesh:
53class Mesh:
54    """Category: **User Interface Classes**"""
55
56    pass

Category: User Interface Classes

class NotFoundError(builtins.Exception):
26class NotFoundError(Exception):
27    """Exception raised when a referenced object does not exist.
28
29    Category: **Exception Classes**
30    """

Exception raised when a referenced object does not exist.

Category: Exception Classes

def open_url(address: str, force_fallback: bool = False) -> None:
1337def open_url(address: str, force_fallback: bool = False) -> None:
1338    """Open the provided URL.
1339
1340    Category: **General Utility Functions**
1341
1342    Attempts to open the provided url in a web-browser. If that is not
1343    possible (or force_fallback is True), instead displays the url as
1344    a string and/or qrcode.
1345    """
1346    return None

Open the provided URL.

Category: General Utility Functions

Attempts to open the provided url in a web-browser. If that is not possible (or force_fallback is True), instead displays the url as a string and/or qrcode.

def overlay_web_browser_close() -> bool:
1349def overlay_web_browser_close() -> bool:
1350    """Close any open overlay web browser.
1351
1352    Category: **General Utility Functions**
1353    """
1354    return bool()

Close any open overlay web browser.

Category: General Utility Functions

def overlay_web_browser_is_open() -> bool:
1357def overlay_web_browser_is_open() -> bool:
1358    """Return whether an overlay web browser is open currently.
1359
1360    Category: **General Utility Functions**
1361    """
1362    return bool()

Return whether an overlay web browser is open currently.

Category: General Utility Functions

def overlay_web_browser_is_supported() -> bool:
1365def overlay_web_browser_is_supported() -> bool:
1366    """Return whether an overlay web browser is supported here.
1367
1368    Category: **General Utility Functions**
1369
1370    An overlay web browser is a small dialog that pops up over the top
1371    of the main engine window. It can be used for performing simple
1372    tasks such as sign-ins.
1373    """
1374    return bool()

Return whether an overlay web browser is supported here.

Category: General Utility Functions

An overlay web browser is a small dialog that pops up over the top of the main engine window. It can be used for performing simple tasks such as sign-ins.

def overlay_web_browser_open_url(address: str) -> None:
1377def overlay_web_browser_open_url(address: str) -> None:
1378    """Open the provided URL in an overlayw web browser.
1379
1380    Category: **General Utility Functions**
1381
1382    An overlay web browser is a small dialog that pops up over the top
1383    of the main engine window. It can be used for performing simple
1384    tasks such as sign-ins.
1385    """
1386    return None

Open the provided URL in an overlayw web browser.

Category: General Utility Functions

An overlay web browser is a small dialog that pops up over the top of the main engine window. It can be used for performing simple tasks such as sign-ins.

class Permission(enum.Enum):
89class Permission(Enum):
90    """Permissions that can be requested from the OS.
91
92    Category: Enums
93    """
94
95    STORAGE = 0

Permissions that can be requested from the OS.

Category: Enums

STORAGE = <Permission.STORAGE: 0>
class Plugin:
322class Plugin:
323    """A plugin to alter app behavior in some way.
324
325    Category: **App Classes**
326
327    Plugins are discoverable by the meta-tag system
328    and the user can select which ones they want to enable.
329    Enabled plugins are then called at specific times as the
330    app is running in order to modify its behavior in some way.
331    """
332
333    def on_app_running(self) -> None:
334        """Called when the app reaches the running state."""
335
336    def on_app_suspend(self) -> None:
337        """Called when the app enters the suspended state."""
338
339    def on_app_unsuspend(self) -> None:
340        """Called when the app exits the suspended state."""
341
342    def on_app_shutdown(self) -> None:
343        """Called when the app is beginning the shutdown process."""
344
345    def on_app_shutdown_complete(self) -> None:
346        """Called when the app has completed the shutdown process."""
347
348    def has_settings_ui(self) -> bool:
349        """Called to ask if we have settings UI we can show."""
350        return False
351
352    def show_settings_ui(self, source_widget: Any | None) -> None:
353        """Called to show our settings UI."""

A plugin to alter app behavior in some way.

Category: App Classes

Plugins are discoverable by the meta-tag system and the user can select which ones they want to enable. Enabled plugins are then called at specific times as the app is running in order to modify its behavior in some way.

def on_app_running(self) -> None:
333    def on_app_running(self) -> None:
334        """Called when the app reaches the running state."""

Called when the app reaches the running state.

def on_app_suspend(self) -> None:
336    def on_app_suspend(self) -> None:
337        """Called when the app enters the suspended state."""

Called when the app enters the suspended state.

def on_app_unsuspend(self) -> None:
339    def on_app_unsuspend(self) -> None:
340        """Called when the app exits the suspended state."""

Called when the app exits the suspended state.

def on_app_shutdown(self) -> None:
342    def on_app_shutdown(self) -> None:
343        """Called when the app is beginning the shutdown process."""

Called when the app is beginning the shutdown process.

def on_app_shutdown_complete(self) -> None:
345    def on_app_shutdown_complete(self) -> None:
346        """Called when the app has completed the shutdown process."""

Called when the app has completed the shutdown process.

def has_settings_ui(self) -> bool:
348    def has_settings_ui(self) -> bool:
349        """Called to ask if we have settings UI we can show."""
350        return False

Called to ask if we have settings UI we can show.

def show_settings_ui(self, source_widget: typing.Any | None) -> None:
352    def show_settings_ui(self, source_widget: Any | None) -> None:
353        """Called to show our settings UI."""

Called to show our settings UI.

class PluginSpec:
227class PluginSpec:
228    """Represents a plugin the engine knows about.
229
230    Category: **App Classes**
231
232    The 'enabled' attr represents whether this plugin is set to load.
233    Getting or setting that attr affects the corresponding app-config
234    key. Remember to commit the app-config after making any changes.
235
236    The 'attempted_load' attr will be True if the engine has attempted
237    to load the plugin. If 'attempted_load' is True for a PluginSpec
238    but the 'plugin' attr is None, it means there was an error loading
239    the plugin. If a plugin's api-version does not match the running
240    app, if a new plugin is detected with auto-enable-plugins disabled,
241    or if the user has explicitly disabled a plugin, the engine will not
242    even attempt to load it.
243    """
244
245    def __init__(self, class_path: str, loadable: bool):
246        self.class_path = class_path
247        self.loadable = loadable
248        self.attempted_load = False
249        self.plugin: Plugin | None = None
250
251    @property
252    def enabled(self) -> bool:
253        """Whether the user wants this plugin to load."""
254        plugstates: dict[str, dict] = _babase.app.config.get('Plugins', {})
255        assert isinstance(plugstates, dict)
256        val = plugstates.get(self.class_path, {}).get('enabled', False) is True
257        return val
258
259    @enabled.setter
260    def enabled(self, val: bool) -> None:
261        plugstates: dict[str, dict] = _babase.app.config.setdefault(
262            'Plugins', {}
263        )
264        assert isinstance(plugstates, dict)
265        plugstate = plugstates.setdefault(self.class_path, {})
266        plugstate['enabled'] = val
267
268    def attempt_load_if_enabled(self) -> Plugin | None:
269        """Possibly load the plugin and log any errors."""
270        from babase._general import getclass
271        from babase._language import Lstr
272
273        assert not self.attempted_load
274        assert self.plugin is None
275
276        if not self.enabled:
277            return None
278        self.attempted_load = True
279        if not self.loadable:
280            return None
281        try:
282            cls = getclass(self.class_path, Plugin, True)
283        except Exception as exc:
284            _babase.getsimplesound('error').play()
285            _babase.screenmessage(
286                Lstr(
287                    resource='pluginClassLoadErrorText',
288                    subs=[
289                        ('${PLUGIN}', self.class_path),
290                        ('${ERROR}', str(exc)),
291                    ],
292                ),
293                color=(1, 0, 0),
294            )
295            logging.exception(
296                "Error loading plugin class '%s'.", self.class_path
297            )
298            return None
299        try:
300            self.plugin = cls()
301            return self.plugin
302        except Exception as exc:
303            from babase import _error
304
305            _babase.getsimplesound('error').play()
306            _babase.screenmessage(
307                Lstr(
308                    resource='pluginInitErrorText',
309                    subs=[
310                        ('${PLUGIN}', self.class_path),
311                        ('${ERROR}', str(exc)),
312                    ],
313                ),
314                color=(1, 0, 0),
315            )
316            logging.exception(
317                "Error initing plugin class: '%s'.", self.class_path
318            )
319        return None

Represents a plugin the engine knows about.

Category: App Classes

The 'enabled' attr represents whether this plugin is set to load. Getting or setting that attr affects the corresponding app-config key. Remember to commit the app-config after making any changes.

The 'attempted_load' attr will be True if the engine has attempted to load the plugin. If 'attempted_load' is True for a PluginSpec but the 'plugin' attr is None, it means there was an error loading the plugin. If a plugin's api-version does not match the running app, if a new plugin is detected with auto-enable-plugins disabled, or if the user has explicitly disabled a plugin, the engine will not even attempt to load it.

PluginSpec(class_path: str, loadable: bool)
245    def __init__(self, class_path: str, loadable: bool):
246        self.class_path = class_path
247        self.loadable = loadable
248        self.attempted_load = False
249        self.plugin: Plugin | None = None
class_path
loadable
attempted_load
plugin: Plugin | None
enabled: bool
251    @property
252    def enabled(self) -> bool:
253        """Whether the user wants this plugin to load."""
254        plugstates: dict[str, dict] = _babase.app.config.get('Plugins', {})
255        assert isinstance(plugstates, dict)
256        val = plugstates.get(self.class_path, {}).get('enabled', False) is True
257        return val

Whether the user wants this plugin to load.

def attempt_load_if_enabled(self) -> Plugin | None:
268    def attempt_load_if_enabled(self) -> Plugin | None:
269        """Possibly load the plugin and log any errors."""
270        from babase._general import getclass
271        from babase._language import Lstr
272
273        assert not self.attempted_load
274        assert self.plugin is None
275
276        if not self.enabled:
277            return None
278        self.attempted_load = True
279        if not self.loadable:
280            return None
281        try:
282            cls = getclass(self.class_path, Plugin, True)
283        except Exception as exc:
284            _babase.getsimplesound('error').play()
285            _babase.screenmessage(
286                Lstr(
287                    resource='pluginClassLoadErrorText',
288                    subs=[
289                        ('${PLUGIN}', self.class_path),
290                        ('${ERROR}', str(exc)),
291                    ],
292                ),
293                color=(1, 0, 0),
294            )
295            logging.exception(
296                "Error loading plugin class '%s'.", self.class_path
297            )
298            return None
299        try:
300            self.plugin = cls()
301            return self.plugin
302        except Exception as exc:
303            from babase import _error
304
305            _babase.getsimplesound('error').play()
306            _babase.screenmessage(
307                Lstr(
308                    resource='pluginInitErrorText',
309                    subs=[
310                        ('${PLUGIN}', self.class_path),
311                        ('${ERROR}', str(exc)),
312                    ],
313                ),
314                color=(1, 0, 0),
315            )
316            logging.exception(
317                "Error initing plugin class: '%s'.", self.class_path
318            )
319        return None

Possibly load the plugin and log any errors.

def pushcall( call: Callable, from_other_thread: bool = False, suppress_other_thread_warning: bool = False, other_thread_use_fg_context: bool = False, raw: bool = False) -> None:
1421def pushcall(
1422    call: Callable,
1423    from_other_thread: bool = False,
1424    suppress_other_thread_warning: bool = False,
1425    other_thread_use_fg_context: bool = False,
1426    raw: bool = False,
1427) -> None:
1428    """Push a call to the logic event-loop.
1429    Category: **General Utility Functions**
1430
1431    This call expects to be used in the logic thread, and will automatically
1432    save and restore the babase.Context to behave seamlessly.
1433
1434    If you want to push a call from outside of the logic thread,
1435    however, you can pass 'from_other_thread' as True. In this case
1436    the call will always run in the UI context_ref on the logic thread
1437    or whichever context_ref is in the foreground if
1438    other_thread_use_fg_context is True.
1439    Passing raw=True will disable thread checks and context_ref sets/restores.
1440    """
1441    return None

Push a call to the logic event-loop. Category: General Utility Functions

This call expects to be used in the logic thread, and will automatically save and restore the babase.Context to behave seamlessly.

If you want to push a call from outside of the logic thread, however, you can pass 'from_other_thread' as True. In this case the call will always run in the UI context_ref on the logic thread or whichever context_ref is in the foreground if other_thread_use_fg_context is True. Passing raw=True will disable thread checks and context_ref sets/restores.

def quit( confirm: bool = False, quit_type: QuitType | None = None) -> None:
1445def quit(
1446    confirm: bool = False, quit_type: babase.QuitType | None = None
1447) -> None:
1448    """Quit the app.
1449
1450    Category: **General Utility Functions**
1451
1452    If 'confirm' is True, a confirm dialog will be presented if conditions
1453    allow; otherwise the quit will still be immediate.
1454    See docs for babase.QuitType for explanations of the optional
1455    'quit_type' arg.
1456    """
1457    return None

Quit the app.

Category: General Utility Functions

If 'confirm' is True, a confirm dialog will be presented if conditions allow; otherwise the quit will still be immediate. See docs for babase.QuitType for explanations of the optional 'quit_type' arg.

class QuitType(enum.Enum):
42class QuitType(Enum):
43    """Types of input a controller can send to the game.
44
45    Category: Enums
46
47    'soft' may hide/reset the app but keep the process running, depending
48       on the platform.
49
50    'back' is a variant of 'soft' which may give 'back-button-pressed'
51       behavior depending on the platform. (returning to some previous
52       activity instead of dumping to the home screen, etc.)
53
54    'hard' leads to the process exiting. This generally should be avoided
55       on platforms such as mobile.
56    """
57
58    SOFT = 0
59    BACK = 1
60    HARD = 2

Types of input a controller can send to the game.

Category: Enums

'soft' may hide/reset the app but keep the process running, depending on the platform.

'back' is a variant of 'soft' which may give 'back-button-pressed' behavior depending on the platform. (returning to some previous activity instead of dumping to the home screen, etc.)

'hard' leads to the process exiting. This generally should be avoided on platforms such as mobile.

SOFT = <QuitType.SOFT: 0>
BACK = <QuitType.BACK: 1>
HARD = <QuitType.HARD: 2>
def root_ui_pause_updates() -> None:
468def root_ui_pause_updates() -> None:
469    """Temporarily pause updates to the root ui for animation purposes."""
470    return None

Temporarily pause updates to the root ui for animation purposes.

def root_ui_resume_updates() -> None:
473def root_ui_resume_updates() -> None:
474    """Temporarily resume updates to the root ui for animation purposes."""
475    return None

Temporarily resume updates to the root ui for animation purposes.

def rowwidget( edit: _bauiv1.Widget | None = None, parent: _bauiv1.Widget | None = None, size: Optional[Sequence[float]] = None, position: Optional[Sequence[float]] = None, background: bool | None = None, selected_child: _bauiv1.Widget | None = None, visible_child: _bauiv1.Widget | None = None, claims_left_right: bool | None = None, selection_loops_to_parent: bool | None = None) -> _bauiv1.Widget:
478def rowwidget(
479    edit: bauiv1.Widget | None = None,
480    parent: bauiv1.Widget | None = None,
481    size: Sequence[float] | None = None,
482    position: Sequence[float] | None = None,
483    background: bool | None = None,
484    selected_child: bauiv1.Widget | None = None,
485    visible_child: bauiv1.Widget | None = None,
486    claims_left_right: bool | None = None,
487    selection_loops_to_parent: bool | None = None,
488) -> bauiv1.Widget:
489    """Create or edit a row widget.
490
491    Category: **User Interface Functions**
492
493    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
494    a new one is created and returned. Arguments that are not set to None
495    are applied to the Widget.
496    """
497    import bauiv1  # pylint: disable=cyclic-import
498
499    return bauiv1.Widget()

Create or edit a row widget.

Category: User Interface Functions

Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise a new one is created and returned. Arguments that are not set to None are applied to the Widget.

def safecolor( color: Sequence[float], target_intensity: float = 0.6) -> tuple[float, ...]:
1495def safecolor(
1496    color: Sequence[float], target_intensity: float = 0.6
1497) -> tuple[float, ...]:
1498    """Given a color tuple, return a color safe to display as text.
1499
1500    Category: **General Utility Functions**
1501
1502    Accepts tuples of length 3 or 4. This will slightly brighten very
1503    dark colors, etc.
1504    """
1505    return (0.0, 0.0, 0.0)

Given a color tuple, return a color safe to display as text.

Category: General Utility Functions

Accepts tuples of length 3 or 4. This will slightly brighten very dark colors, etc.

def screenmessage( message: str | Lstr, color: Optional[Sequence[float]] = None, log: bool = False) -> None:
1508def screenmessage(
1509    message: str | babase.Lstr,
1510    color: Sequence[float] | None = None,
1511    log: bool = False,
1512) -> None:
1513    """Print a message to the local client's screen, in a given color.
1514
1515    Category: **General Utility Functions**
1516
1517    Note that this version of the function is purely for local display.
1518    To broadcast screen messages in network play, look for methods such as
1519    broadcastmessage() provided by the scene-version packages.
1520    """
1521    return None

Print a message to the local client's screen, in a given color.

Category: General Utility Functions

Note that this version of the function is purely for local display. To broadcast screen messages in network play, look for methods such as broadcastmessage() provided by the scene-version packages.

def scrollwidget( *, edit: _bauiv1.Widget | None = None, parent: _bauiv1.Widget | None = None, size: Optional[Sequence[float]] = None, position: Optional[Sequence[float]] = None, background: bool | None = None, selected_child: _bauiv1.Widget | None = None, capture_arrows: bool = False, on_select_call: Optional[Callable] = None, center_small_content: bool | None = None, center_small_content_horizontally: bool | None = None, color: Optional[Sequence[float]] = None, highlight: bool | None = None, border_opacity: float | None = None, simple_culling_v: float | None = None, selection_loops_to_parent: bool | None = None, claims_left_right: bool | None = None, claims_up_down: bool | None = None, autoselect: bool | None = None) -> _bauiv1.Widget:
502def scrollwidget(
503    *,
504    edit: bauiv1.Widget | None = None,
505    parent: bauiv1.Widget | None = None,
506    size: Sequence[float] | None = None,
507    position: Sequence[float] | None = None,
508    background: bool | None = None,
509    selected_child: bauiv1.Widget | None = None,
510    capture_arrows: bool = False,
511    on_select_call: Callable | None = None,
512    center_small_content: bool | None = None,
513    center_small_content_horizontally: bool | None = None,
514    color: Sequence[float] | None = None,
515    highlight: bool | None = None,
516    border_opacity: float | None = None,
517    simple_culling_v: float | None = None,
518    selection_loops_to_parent: bool | None = None,
519    claims_left_right: bool | None = None,
520    claims_up_down: bool | None = None,
521    autoselect: bool | None = None,
522) -> bauiv1.Widget:
523    """Create or edit a scroll widget.
524
525    Category: **User Interface Functions**
526
527    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
528    a new one is created and returned. Arguments that are not set to None
529    are applied to the Widget.
530    """
531    import bauiv1  # pylint: disable=cyclic-import
532
533    return bauiv1.Widget()

Create or edit a scroll widget.

Category: User Interface Functions

Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise a new one is created and returned. Arguments that are not set to None are applied to the Widget.

def set_analytics_screen(screen: str) -> None:
1524def set_analytics_screen(screen: str) -> None:
1525    """Used for analytics to see where in the app players spend their time.
1526
1527    Category: **General Utility Functions**
1528
1529    Generally called when opening a new window or entering some UI.
1530    'screen' should be a string description of an app location
1531    ('Main Menu', etc.)
1532    """
1533    return None

Used for analytics to see where in the app players spend their time.

Category: General Utility Functions

Generally called when opening a new window or entering some UI. 'screen' should be a string description of an app location ('Main Menu', etc.)

class Sound:
59class Sound:
60    """Category: **User Interface Classes**"""
61
62    def play(self, volume: float = 1.0) -> None:
63        """Play the sound locally."""
64        return None
65
66    def stop(self) -> None:
67        """Stop the sound if it is playing."""
68        return None

Category: User Interface Classes

def play(self, volume: float = 1.0) -> None:
62    def play(self, volume: float = 1.0) -> None:
63        """Play the sound locally."""
64        return None

Play the sound locally.

def stop(self) -> None:
66    def stop(self) -> None:
67        """Stop the sound if it is playing."""
68        return None

Stop the sound if it is playing.

class SpecialChar(enum.Enum):
 98class SpecialChar(Enum):
 99    """Special characters the game can print.
100
101    Category: Enums
102    """
103
104    DOWN_ARROW = 0
105    UP_ARROW = 1
106    LEFT_ARROW = 2
107    RIGHT_ARROW = 3
108    TOP_BUTTON = 4
109    LEFT_BUTTON = 5
110    RIGHT_BUTTON = 6
111    BOTTOM_BUTTON = 7
112    DELETE = 8
113    SHIFT = 9
114    BACK = 10
115    LOGO_FLAT = 11
116    REWIND_BUTTON = 12
117    PLAY_PAUSE_BUTTON = 13
118    FAST_FORWARD_BUTTON = 14
119    DPAD_CENTER_BUTTON = 15
120    PLAY_STATION_CROSS_BUTTON = 16
121    PLAY_STATION_CIRCLE_BUTTON = 17
122    PLAY_STATION_TRIANGLE_BUTTON = 18
123    PLAY_STATION_SQUARE_BUTTON = 19
124    PLAY_BUTTON = 20
125    PAUSE_BUTTON = 21
126    OUYA_BUTTON_O = 22
127    OUYA_BUTTON_U = 23
128    OUYA_BUTTON_Y = 24
129    OUYA_BUTTON_A = 25
130    TOKEN = 26
131    LOGO = 27
132    TICKET = 28
133    GOOGLE_PLAY_GAMES_LOGO = 29
134    GAME_CENTER_LOGO = 30
135    DICE_BUTTON1 = 31
136    DICE_BUTTON2 = 32
137    DICE_BUTTON3 = 33
138    DICE_BUTTON4 = 34
139    GAME_CIRCLE_LOGO = 35
140    PARTY_ICON = 36
141    TEST_ACCOUNT = 37
142    TICKET_BACKING = 38
143    TROPHY1 = 39
144    TROPHY2 = 40
145    TROPHY3 = 41
146    TROPHY0A = 42
147    TROPHY0B = 43
148    TROPHY4 = 44
149    LOCAL_ACCOUNT = 45
150    EXPLODINARY_LOGO = 46
151    FLAG_UNITED_STATES = 47
152    FLAG_MEXICO = 48
153    FLAG_GERMANY = 49
154    FLAG_BRAZIL = 50
155    FLAG_RUSSIA = 51
156    FLAG_CHINA = 52
157    FLAG_UNITED_KINGDOM = 53
158    FLAG_CANADA = 54
159    FLAG_INDIA = 55
160    FLAG_JAPAN = 56
161    FLAG_FRANCE = 57
162    FLAG_INDONESIA = 58
163    FLAG_ITALY = 59
164    FLAG_SOUTH_KOREA = 60
165    FLAG_NETHERLANDS = 61
166    FEDORA = 62
167    HAL = 63
168    CROWN = 64
169    YIN_YANG = 65
170    EYE_BALL = 66
171    SKULL = 67
172    HEART = 68
173    DRAGON = 69
174    HELMET = 70
175    MUSHROOM = 71
176    NINJA_STAR = 72
177    VIKING_HELMET = 73
178    MOON = 74
179    SPIDER = 75
180    FIREBALL = 76
181    FLAG_UNITED_ARAB_EMIRATES = 77
182    FLAG_QATAR = 78
183    FLAG_EGYPT = 79
184    FLAG_KUWAIT = 80
185    FLAG_ALGERIA = 81
186    FLAG_SAUDI_ARABIA = 82
187    FLAG_MALAYSIA = 83
188    FLAG_CZECH_REPUBLIC = 84
189    FLAG_AUSTRALIA = 85
190    FLAG_SINGAPORE = 86
191    OCULUS_LOGO = 87
192    STEAM_LOGO = 88
193    NVIDIA_LOGO = 89
194    FLAG_IRAN = 90
195    FLAG_POLAND = 91
196    FLAG_ARGENTINA = 92
197    FLAG_PHILIPPINES = 93
198    FLAG_CHILE = 94
199    MIKIROG = 95
200    V2_LOGO = 96

Special characters the game can print.

Category: Enums

DOWN_ARROW = <SpecialChar.DOWN_ARROW: 0>
UP_ARROW = <SpecialChar.UP_ARROW: 1>
LEFT_ARROW = <SpecialChar.LEFT_ARROW: 2>
RIGHT_ARROW = <SpecialChar.RIGHT_ARROW: 3>
TOP_BUTTON = <SpecialChar.TOP_BUTTON: 4>
LEFT_BUTTON = <SpecialChar.LEFT_BUTTON: 5>
RIGHT_BUTTON = <SpecialChar.RIGHT_BUTTON: 6>
BOTTOM_BUTTON = <SpecialChar.BOTTOM_BUTTON: 7>
DELETE = <SpecialChar.DELETE: 8>
SHIFT = <SpecialChar.SHIFT: 9>
BACK = <SpecialChar.BACK: 10>
LOGO_FLAT = <SpecialChar.LOGO_FLAT: 11>
REWIND_BUTTON = <SpecialChar.REWIND_BUTTON: 12>
PLAY_PAUSE_BUTTON = <SpecialChar.PLAY_PAUSE_BUTTON: 13>
FAST_FORWARD_BUTTON = <SpecialChar.FAST_FORWARD_BUTTON: 14>
DPAD_CENTER_BUTTON = <SpecialChar.DPAD_CENTER_BUTTON: 15>
PLAY_STATION_CROSS_BUTTON = <SpecialChar.PLAY_STATION_CROSS_BUTTON: 16>
PLAY_STATION_CIRCLE_BUTTON = <SpecialChar.PLAY_STATION_CIRCLE_BUTTON: 17>
PLAY_STATION_TRIANGLE_BUTTON = <SpecialChar.PLAY_STATION_TRIANGLE_BUTTON: 18>
PLAY_STATION_SQUARE_BUTTON = <SpecialChar.PLAY_STATION_SQUARE_BUTTON: 19>
PLAY_BUTTON = <SpecialChar.PLAY_BUTTON: 20>
PAUSE_BUTTON = <SpecialChar.PAUSE_BUTTON: 21>
OUYA_BUTTON_O = <SpecialChar.OUYA_BUTTON_O: 22>
OUYA_BUTTON_U = <SpecialChar.OUYA_BUTTON_U: 23>
OUYA_BUTTON_Y = <SpecialChar.OUYA_BUTTON_Y: 24>
OUYA_BUTTON_A = <SpecialChar.OUYA_BUTTON_A: 25>
TOKEN = <SpecialChar.TOKEN: 26>
TICKET = <SpecialChar.TICKET: 28>
DICE_BUTTON1 = <SpecialChar.DICE_BUTTON1: 31>
DICE_BUTTON2 = <SpecialChar.DICE_BUTTON2: 32>
DICE_BUTTON3 = <SpecialChar.DICE_BUTTON3: 33>
DICE_BUTTON4 = <SpecialChar.DICE_BUTTON4: 34>
PARTY_ICON = <SpecialChar.PARTY_ICON: 36>
TEST_ACCOUNT = <SpecialChar.TEST_ACCOUNT: 37>
TICKET_BACKING = <SpecialChar.TICKET_BACKING: 38>
TROPHY1 = <SpecialChar.TROPHY1: 39>
TROPHY2 = <SpecialChar.TROPHY2: 40>
TROPHY3 = <SpecialChar.TROPHY3: 41>
TROPHY0A = <SpecialChar.TROPHY0A: 42>
TROPHY0B = <SpecialChar.TROPHY0B: 43>
TROPHY4 = <SpecialChar.TROPHY4: 44>
LOCAL_ACCOUNT = <SpecialChar.LOCAL_ACCOUNT: 45>
FLAG_UNITED_STATES = <SpecialChar.FLAG_UNITED_STATES: 47>
FLAG_MEXICO = <SpecialChar.FLAG_MEXICO: 48>
FLAG_GERMANY = <SpecialChar.FLAG_GERMANY: 49>
FLAG_BRAZIL = <SpecialChar.FLAG_BRAZIL: 50>
FLAG_RUSSIA = <SpecialChar.FLAG_RUSSIA: 51>
FLAG_CHINA = <SpecialChar.FLAG_CHINA: 52>
FLAG_UNITED_KINGDOM = <SpecialChar.FLAG_UNITED_KINGDOM: 53>
FLAG_CANADA = <SpecialChar.FLAG_CANADA: 54>
FLAG_INDIA = <SpecialChar.FLAG_INDIA: 55>
FLAG_JAPAN = <SpecialChar.FLAG_JAPAN: 56>
FLAG_FRANCE = <SpecialChar.FLAG_FRANCE: 57>
FLAG_INDONESIA = <SpecialChar.FLAG_INDONESIA: 58>
FLAG_ITALY = <SpecialChar.FLAG_ITALY: 59>
FLAG_SOUTH_KOREA = <SpecialChar.FLAG_SOUTH_KOREA: 60>
FLAG_NETHERLANDS = <SpecialChar.FLAG_NETHERLANDS: 61>
FEDORA = <SpecialChar.FEDORA: 62>
HAL = <SpecialChar.HAL: 63>
CROWN = <SpecialChar.CROWN: 64>
YIN_YANG = <SpecialChar.YIN_YANG: 65>
EYE_BALL = <SpecialChar.EYE_BALL: 66>
SKULL = <SpecialChar.SKULL: 67>
HEART = <SpecialChar.HEART: 68>
DRAGON = <SpecialChar.DRAGON: 69>
HELMET = <SpecialChar.HELMET: 70>
MUSHROOM = <SpecialChar.MUSHROOM: 71>
NINJA_STAR = <SpecialChar.NINJA_STAR: 72>
VIKING_HELMET = <SpecialChar.VIKING_HELMET: 73>
MOON = <SpecialChar.MOON: 74>
SPIDER = <SpecialChar.SPIDER: 75>
FIREBALL = <SpecialChar.FIREBALL: 76>
FLAG_UNITED_ARAB_EMIRATES = <SpecialChar.FLAG_UNITED_ARAB_EMIRATES: 77>
FLAG_QATAR = <SpecialChar.FLAG_QATAR: 78>
FLAG_EGYPT = <SpecialChar.FLAG_EGYPT: 79>
FLAG_KUWAIT = <SpecialChar.FLAG_KUWAIT: 80>
FLAG_ALGERIA = <SpecialChar.FLAG_ALGERIA: 81>
FLAG_SAUDI_ARABIA = <SpecialChar.FLAG_SAUDI_ARABIA: 82>
FLAG_MALAYSIA = <SpecialChar.FLAG_MALAYSIA: 83>
FLAG_CZECH_REPUBLIC = <SpecialChar.FLAG_CZECH_REPUBLIC: 84>
FLAG_AUSTRALIA = <SpecialChar.FLAG_AUSTRALIA: 85>
FLAG_SINGAPORE = <SpecialChar.FLAG_SINGAPORE: 86>
FLAG_IRAN = <SpecialChar.FLAG_IRAN: 90>
FLAG_POLAND = <SpecialChar.FLAG_POLAND: 91>
FLAG_ARGENTINA = <SpecialChar.FLAG_ARGENTINA: 92>
FLAG_PHILIPPINES = <SpecialChar.FLAG_PHILIPPINES: 93>
FLAG_CHILE = <SpecialChar.FLAG_CHILE: 94>
MIKIROG = <SpecialChar.MIKIROG: 95>
def spinnerwidget( *, edit: _bauiv1.Widget | None = None, parent: _bauiv1.Widget | None = None, size: float | None = None, position: Optional[Sequence[float]] = None, style: Optional[Literal['bomb', 'simple']] = None, visible: bool | None = None) -> _bauiv1.Widget:
541def spinnerwidget(
542    *,
543    edit: bauiv1.Widget | None = None,
544    parent: bauiv1.Widget | None = None,
545    size: float | None = None,
546    position: Sequence[float] | None = None,
547    style: Literal['bomb', 'simple'] | None = None,
548    visible: bool | None = None,
549) -> bauiv1.Widget:
550    """Create or edit a spinner widget.
551
552    Category: **User Interface Functions**
553
554    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
555    a new one is created and returned. Arguments that are not set to None
556    are applied to the Widget.
557    """
558    import bauiv1  # pylint: disable=cyclic-import
559
560    return bauiv1.Widget()

Create or edit a spinner widget.

Category: User Interface Functions

Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise a new one is created and returned. Arguments that are not set to None are applied to the Widget.

def supports_unicode_display() -> bool:
1673def supports_unicode_display() -> bool:
1674    """Return whether we can display all unicode characters in the gui."""
1675    return bool()

Return whether we can display all unicode characters in the gui.

class Texture:
71class Texture:
72    """Category: **User Interface Classes**"""
73
74    pass

Category: User Interface Classes

def textwidget( *, edit: _bauiv1.Widget | None = None, parent: _bauiv1.Widget | None = None, size: Optional[Sequence[float]] = None, position: Optional[Sequence[float]] = None, text: str | Lstr | None = None, v_align: str | None = None, h_align: str | None = None, editable: bool | None = None, padding: float | None = None, on_return_press_call: Optional[Callable[[], NoneType]] = None, on_activate_call: Optional[Callable[[], NoneType]] = None, selectable: bool | None = None, query: _bauiv1.Widget | None = None, max_chars: int | None = None, color: Optional[Sequence[float]] = None, click_activate: bool | None = None, on_select_call: Optional[Callable[[], NoneType]] = None, always_highlight: bool | None = None, draw_controller: _bauiv1.Widget | None = None, scale: float | None = None, corner_scale: float | None = None, description: str | Lstr | None = None, transition_delay: float | None = None, maxwidth: float | None = None, max_height: float | None = None, flatness: float | None = None, shadow: float | None = None, autoselect: bool | None = None, rotate: float | None = None, enabled: bool | None = None, force_internal_editing: bool | None = None, always_show_carat: bool | None = None, big: bool | None = None, extra_touch_border_scale: float | None = None, res_scale: float | None = None, query_max_chars: _bauiv1.Widget | None = None, query_description: _bauiv1.Widget | None = None, adapter_finished: bool | None = None, glow_type: str | None = None, allow_clear_button: bool | None = None) -> _bauiv1.Widget:
563def textwidget(
564    *,
565    edit: bauiv1.Widget | None = None,
566    parent: bauiv1.Widget | None = None,
567    size: Sequence[float] | None = None,
568    position: Sequence[float] | None = None,
569    text: str | bauiv1.Lstr | None = None,
570    v_align: str | None = None,
571    h_align: str | None = None,
572    editable: bool | None = None,
573    padding: float | None = None,
574    on_return_press_call: Callable[[], None] | None = None,
575    on_activate_call: Callable[[], None] | None = None,
576    selectable: bool | None = None,
577    query: bauiv1.Widget | None = None,
578    max_chars: int | None = None,
579    color: Sequence[float] | None = None,
580    click_activate: bool | None = None,
581    on_select_call: Callable[[], None] | None = None,
582    always_highlight: bool | None = None,
583    draw_controller: bauiv1.Widget | None = None,
584    scale: float | None = None,
585    corner_scale: float | None = None,
586    description: str | bauiv1.Lstr | None = None,
587    transition_delay: float | None = None,
588    maxwidth: float | None = None,
589    max_height: float | None = None,
590    flatness: float | None = None,
591    shadow: float | None = None,
592    autoselect: bool | None = None,
593    rotate: float | None = None,
594    enabled: bool | None = None,
595    force_internal_editing: bool | None = None,
596    always_show_carat: bool | None = None,
597    big: bool | None = None,
598    extra_touch_border_scale: float | None = None,
599    res_scale: float | None = None,
600    query_max_chars: bauiv1.Widget | None = None,
601    query_description: bauiv1.Widget | None = None,
602    adapter_finished: bool | None = None,
603    glow_type: str | None = None,
604    allow_clear_button: bool | None = None,
605) -> bauiv1.Widget:
606    """Create or edit a text widget.
607
608    Category: **User Interface Functions**
609
610    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
611    a new one is created and returned. Arguments that are not set to None
612    are applied to the Widget.
613    """
614    import bauiv1  # pylint: disable=cyclic-import
615
616    return bauiv1.Widget()

Create or edit a text widget.

Category: User Interface Functions

Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise a new one is created and returned. Arguments that are not set to None are applied to the Widget.

def timestring(timeval: float | int, centi: bool = True) -> Lstr:
15def timestring(
16    timeval: float | int,
17    centi: bool = True,
18) -> babase.Lstr:
19    """Generate a babase.Lstr for displaying a time value.
20
21    Category: **General Utility Functions**
22
23    Given a time value, returns a babase.Lstr with:
24    (hours if > 0 ) : minutes : seconds : (centiseconds if centi=True).
25
26    WARNING: the underlying Lstr value is somewhat large so don't use this
27    to rapidly update Node text values for an onscreen timer or you may
28    consume significant network bandwidth.  For that purpose you should
29    use a 'timedisplay' Node and attribute connections.
30
31    """
32    from babase._language import Lstr
33
34    # We take float seconds but operate on int milliseconds internally.
35    timeval = int(1000 * timeval)
36    bits = []
37    subs = []
38    hval = (timeval // 1000) // (60 * 60)
39    if hval != 0:
40        bits.append('${H}')
41        subs.append(
42            (
43                '${H}',
44                Lstr(
45                    resource='timeSuffixHoursText',
46                    subs=[('${COUNT}', str(hval))],
47                ),
48            )
49        )
50    mval = ((timeval // 1000) // 60) % 60
51    if mval != 0:
52        bits.append('${M}')
53        subs.append(
54            (
55                '${M}',
56                Lstr(
57                    resource='timeSuffixMinutesText',
58                    subs=[('${COUNT}', str(mval))],
59                ),
60            )
61        )
62
63    # We add seconds if its non-zero *or* we haven't added anything else.
64    if centi:
65        # pylint: disable=consider-using-f-string
66        sval = timeval / 1000.0 % 60.0
67        if sval >= 0.005 or not bits:
68            bits.append('${S}')
69            subs.append(
70                (
71                    '${S}',
72                    Lstr(
73                        resource='timeSuffixSecondsText',
74                        subs=[('${COUNT}', ('%.2f' % sval))],
75                    ),
76                )
77            )
78    else:
79        sval = timeval // 1000 % 60
80        if sval != 0 or not bits:
81            bits.append('${S}')
82            subs.append(
83                (
84                    '${S}',
85                    Lstr(
86                        resource='timeSuffixSecondsText',
87                        subs=[('${COUNT}', str(sval))],
88                    ),
89                )
90            )
91    return Lstr(value=' '.join(bits), subs=subs)

Generate a babase.Lstr for displaying a time value.

Category: General Utility Functions

Given a time value, returns a babase.Lstr with: (hours if > 0 ) : minutes : seconds : (centiseconds if centi=True).

WARNING: the underlying Lstr value is somewhat large so don't use this to rapidly update Node text values for an onscreen timer or you may consume significant network bandwidth. For that purpose you should use a 'timedisplay' Node and attribute connections.

def uicleanupcheck(obj: Any, widget: _bauiv1.Widget) -> None:
305def uicleanupcheck(obj: Any, widget: bauiv1.Widget) -> None:
306    """Checks to ensure a widget-owning object gets cleaned up properly.
307
308    Category: User Interface Functions
309
310    This adds a check which will print an error message if the provided
311    object still exists ~5 seconds after the provided bauiv1.Widget dies.
312
313    This is a good sanity check for any sort of object that wraps or
314    controls a bauiv1.Widget. For instance, a 'Window' class instance has
315    no reason to still exist once its root container bauiv1.Widget has fully
316    transitioned out and been destroyed. Circular references or careless
317    strong referencing can lead to such objects never getting destroyed,
318    however, and this helps detect such cases to avoid memory leaks.
319    """
320    if DEBUG_UI_CLEANUP_CHECKS:
321        print(f'adding uicleanup to {obj}')
322    if not isinstance(widget, _bauiv1.Widget):
323        raise TypeError('widget arg is not a bauiv1.Widget')
324
325    if bool(False):
326
327        def foobar() -> None:
328            """Just testing."""
329            if DEBUG_UI_CLEANUP_CHECKS:
330                print('uicleanupcheck widget dying...')
331
332        widget.add_delete_callback(foobar)
333
334    assert babase.app.classic is not None
335    babase.app.ui_v1.cleanupchecks.append(
336        UICleanupCheck(
337            obj=weakref.ref(obj), widget=widget, widget_death_time=None
338        )
339    )

Checks to ensure a widget-owning object gets cleaned up properly.

Category: User Interface Functions

This adds a check which will print an error message if the provided object still exists ~5 seconds after the provided bauiv1.Widget dies.

This is a good sanity check for any sort of object that wraps or controls a bauiv1.Widget. For instance, a 'Window' class instance has no reason to still exist once its root container bauiv1.Widget has fully transitioned out and been destroyed. Circular references or careless strong referencing can lead to such objects never getting destroyed, however, and this helps detect such cases to avoid memory leaks.

class UIScale(enum.Enum):
63class UIScale(Enum):
64    """The overall scale the UI is being rendered for. Note that this is
65    independent of pixel resolution. For example, a phone and a desktop PC
66    might render the game at similar pixel resolutions but the size they
67    display content at will vary significantly.
68
69    Category: Enums
70
71    'large' is used for devices such as desktop PCs where fine details can
72       be clearly seen. UI elements are generally smaller on the screen
73       and more content can be seen at once.
74
75    'medium' is used for devices such as tablets, TVs, or VR headsets.
76       This mode strikes a balance between clean readability and amount of
77       content visible.
78
79    'small' is used primarily for phones or other small devices where
80       content needs to be presented as large and clear in order to remain
81       readable from an average distance.
82    """
83
84    SMALL = 0
85    MEDIUM = 1
86    LARGE = 2

The overall scale the UI is being rendered for. Note that this is independent of pixel resolution. For example, a phone and a desktop PC might render the game at similar pixel resolutions but the size they display content at will vary significantly.

Category: Enums

'large' is used for devices such as desktop PCs where fine details can be clearly seen. UI elements are generally smaller on the screen and more content can be seen at once.

'medium' is used for devices such as tablets, TVs, or VR headsets. This mode strikes a balance between clean readability and amount of content visible.

'small' is used primarily for phones or other small devices where content needs to be presented as large and clear in order to remain readable from an average distance.

SMALL = <UIScale.SMALL: 0>
MEDIUM = <UIScale.MEDIUM: 1>
LARGE = <UIScale.LARGE: 2>
class UIV1AppSubsystem(babase._appsubsystem.AppSubsystem):
 33class UIV1AppSubsystem(babase.AppSubsystem):
 34    """Consolidated UI functionality for the app.
 35
 36    Category: **App Classes**
 37
 38    To use this class, access the single instance of it at 'ba.app.ui'.
 39    """
 40
 41    class RootUIElement(Enum):
 42        """Stuff provided by the root ui."""
 43
 44        MENU_BUTTON = 'menu_button'
 45        SQUAD_BUTTON = 'squad_button'
 46        ACCOUNT_BUTTON = 'account_button'
 47        SETTINGS_BUTTON = 'settings_button'
 48        INBOX_BUTTON = 'inbox_button'
 49        STORE_BUTTON = 'store_button'
 50        INVENTORY_BUTTON = 'inventory_button'
 51        ACHIEVEMENTS_BUTTON = 'achievements_button'
 52        GET_TOKENS_BUTTON = 'get_tokens_button'
 53        TICKETS_METER = 'tickets_meter'
 54        TOKENS_METER = 'tokens_meter'
 55        TROPHY_METER = 'trophy_meter'
 56        LEVEL_METER = 'level_meter'
 57        CHEST_SLOT_0 = 'chest_slot_0'
 58        CHEST_SLOT_1 = 'chest_slot_1'
 59        CHEST_SLOT_2 = 'chest_slot_2'
 60        CHEST_SLOT_3 = 'chest_slot_3'
 61
 62    def __init__(self) -> None:
 63        from bauiv1._uitypes import MainWindow
 64
 65        super().__init__()
 66
 67        # We hold only a weak ref to the current main Window; we want it
 68        # to be able to disappear on its own. That being said, we do
 69        # expect MainWindows to keep themselves alive until replaced by
 70        # another MainWindow and we complain if they don't.
 71        self._main_window = empty_weakref(MainWindow)
 72        self._main_window_widget: bauiv1.Widget | None = None
 73
 74        self.quit_window: bauiv1.Widget | None = None
 75
 76        # For storing arbitrary class-level state data for Windows or
 77        # other UI related classes.
 78        self.window_states: dict[type, Any] = {}
 79
 80        self._uiscale: babase.UIScale
 81        self._update_ui_scale()
 82
 83        self.cleanupchecks: list[UICleanupCheck] = []
 84        self.upkeeptimer: babase.AppTimer | None = None
 85
 86        self.title_color = (0.72, 0.7, 0.75)
 87        self.heading_color = (0.72, 0.7, 0.75)
 88        self.infotextcolor = (0.7, 0.9, 0.7)
 89
 90        self._last_win_recreate_size: tuple[float, float] | None = None
 91        self._last_screen_size_win_recreate_time: float | None = None
 92        self._screen_size_win_recreate_timer: babase.AppTimer | None = None
 93
 94        # Elements in our root UI will call anything here when
 95        # activated.
 96        self.root_ui_calls: dict[
 97            UIV1AppSubsystem.RootUIElement, Callable[[], None]
 98        ] = {}
 99
100    def _update_ui_scale(self) -> None:
101        uiscalestr = babase.get_ui_scale()
102        if uiscalestr == 'large':
103            self._uiscale = babase.UIScale.LARGE
104        elif uiscalestr == 'medium':
105            self._uiscale = babase.UIScale.MEDIUM
106        elif uiscalestr == 'small':
107            self._uiscale = babase.UIScale.SMALL
108        else:
109            logging.error("Invalid UIScale '%s'.", uiscalestr)
110            self._uiscale = babase.UIScale.MEDIUM
111
112    @property
113    def available(self) -> bool:
114        """Can uiv1 currently be used?
115
116        Code that may run in headless mode, before the UI has been spun up,
117        while other ui systems are active, etc. can check this to avoid
118        likely erroring.
119        """
120        return _bauiv1.is_available()
121
122    @override
123    def reset(self) -> None:
124        from bauiv1._uitypes import MainWindow
125
126        self.root_ui_calls.clear()
127        self._main_window = empty_weakref(MainWindow)
128        self._main_window_widget = None
129
130    @property
131    def uiscale(self) -> babase.UIScale:
132        """Current ui scale for the app."""
133        return self._uiscale
134
135    @override
136    def on_app_loading(self) -> None:
137        from bauiv1._uitypes import ui_upkeep
138
139        # IMPORTANT: If tweaking UI stuff, make sure it behaves for
140        # small, medium, and large UI modes. (doesn't run off screen,
141        # etc). The overrides below can be used to test with different
142        # sizes. Generally small is used on phones, medium is used on
143        # tablets/tvs, and large is on desktop computers or perhaps
144        # large tablets. When possible, run in windowed mode and resize
145        # the window to assure this holds true at all aspect ratios.
146
147        # UPDATE: A better way to test this is now by setting the
148        # environment variable BA_UI_SCALE to "small", "medium", or
149        # "large". This will affect system UIs not covered by the values
150        # below such as screen-messages. The below values remain
151        # functional, however, for cases such as Android where
152        # environment variables can't be set easily.
153
154        if bool(False):  # force-test ui scale
155            self._uiscale = babase.UIScale.SMALL
156            with babase.ContextRef.empty():
157                babase.pushcall(
158                    lambda: babase.screenmessage(
159                        f'FORCING UISCALE {self._uiscale.name} FOR TESTING',
160                        color=(1, 0, 1),
161                        log=True,
162                    )
163                )
164
165        # Kick off our periodic UI upkeep.
166
167        # FIXME: Can probably kill this if we do immediate UI death
168        # checks.
169        self.upkeeptimer = babase.AppTimer(2.6543, ui_upkeep, repeat=True)
170
171    def get_main_window(self) -> bauiv1.MainWindow | None:
172        """Return main window, if any."""
173        return self._main_window()
174
175    def set_main_window(
176        self,
177        window: bauiv1.MainWindow,
178        *,
179        from_window: bauiv1.MainWindow | None | bool = True,
180        is_back: bool = False,
181        is_top_level: bool = False,
182        is_auxiliary: bool = False,
183        back_state: MainWindowState | None = None,
184        suppress_warning: bool = False,
185    ) -> None:
186        """Set the current 'main' window.
187
188        Generally this should not be called directly; The high level
189        MainWindow methods main_window_replace() and main_window_back()
190        should be used whenever possible to implement navigation.
191
192        The caller is responsible for cleaning up any previous main
193        window.
194        """
195        # pylint: disable=too-many-locals
196        # pylint: disable=too-many-branches
197        # pylint: disable=too-many-statements
198        from bauiv1._uitypes import MainWindow
199
200        # Encourage migration to the new higher level nav calls.
201        if not suppress_warning:
202            warnings.warn(
203                'set_main_window() should usually not be called directly;'
204                ' use the main_window_replace() or main_window_back()'
205                ' methods on MainWindow objects for navigation instead.'
206                ' If you truly need to use set_main_window(),'
207                ' pass suppress_warning=True to silence this warning.',
208                DeprecationWarning,
209                stacklevel=2,
210            )
211
212        # We used to accept Widgets but now want MainWindows.
213        if not isinstance(window, MainWindow):
214            raise RuntimeError(
215                f'set_main_window() now takes a MainWindow as its "window" arg.'
216                f' You passed a {type(window)}.',
217            )
218        window_weakref = weakref.ref(window)
219        window_widget = window.get_root_widget()
220
221        if not isinstance(from_window, MainWindow):
222            if from_window is not None and not isinstance(from_window, bool):
223                raise RuntimeError(
224                    f'set_main_window() now takes a MainWindow or bool or None'
225                    f'as its "from_window" arg.'
226                    f' You passed a {type(from_window)}.',
227                )
228
229        existing = self._main_window()
230
231        # If they passed a back-state, make sure it is fully filled out.
232        if back_state is not None:
233            if (
234                back_state.is_top_level is None
235                or back_state.is_auxiliary is None
236                or back_state.window_type is None
237            ):
238                raise RuntimeError(
239                    'Provided back_state is incomplete.'
240                    ' Make sure to only pass fully-filled-out MainWindowStates.'
241                )
242
243        # If a top-level main-window is being set, complain if there already
244        # is a main-window.
245        if is_top_level:
246            if existing:
247                logging.warning(
248                    'set_main_window() called with top-level window %s'
249                    ' but found existing main-window %s.',
250                    window,
251                    existing,
252                )
253        else:
254            # In other cases, sanity-check that the window asking for
255            # this switch is the one we're switching away from.
256            try:
257                if isinstance(from_window, bool):
258                    # For default val True we warn that the arg wasn't
259                    # passed. False can be explicitly passed to disable
260                    # this check.
261                    if from_window is True:
262                        caller_frame = inspect.stack()[1]
263                        caller_filename = caller_frame.filename
264                        caller_line_number = caller_frame.lineno
265                        logging.warning(
266                            'set_main_window() should be passed a'
267                            " 'from_window' value to help ensure proper"
268                            ' UI behavior (%s line %i).',
269                            caller_filename,
270                            caller_line_number,
271                        )
272                else:
273                    # For everything else, warn if what they passed
274                    # wasn't the previous main menu widget.
275                    if from_window is not existing:
276                        caller_frame = inspect.stack()[1]
277                        caller_filename = caller_frame.filename
278                        caller_line_number = caller_frame.lineno
279                        logging.warning(
280                            "set_main_window() was passed 'from_window' %s"
281                            ' but existing main-menu-window is %s.'
282                            ' (%s line %i).',
283                            from_window,
284                            existing,
285                            caller_filename,
286                            caller_line_number,
287                        )
288            except Exception:
289                # Prevent any bugs in these checks from causing problems.
290                logging.exception('Error checking from_window')
291
292        if is_back:
293            # These values should only be passed for forward navigation.
294            assert not is_top_level
295            assert not is_auxiliary
296            # Make sure back state is complete.
297            assert back_state is not None
298            assert back_state.is_top_level is not None
299            assert back_state.is_auxiliary is not None
300            assert back_state.window_type is type(window)
301            window.main_window_back_state = back_state.parent
302            window.main_window_is_top_level = back_state.is_top_level
303            window.main_window_is_auxiliary = back_state.is_auxiliary
304        else:
305            # Store if the window is top-level so we won't complain later if
306            # we go back from it and there's nowhere to go to.
307            window.main_window_is_top_level = is_top_level
308
309            window.main_window_is_auxiliary = is_auxiliary
310
311            # When navigating forward, generate a back-window-state from
312            # the outgoing window.
313            if is_top_level:
314                # Top level windows don't have or expect anywhere to
315                # go back to.
316                window.main_window_back_state = None
317            elif back_state is not None:
318                window.main_window_back_state = back_state
319            else:
320                oldwin = self._main_window()
321                if oldwin is None:
322                    # We currenty only hold weak refs to windows so that
323                    # they are free to die on their own, but we expect
324                    # the main menu window to keep itself alive as long
325                    # as its the main one. Holler if that seems to not
326                    # be happening.
327                    logging.warning(
328                        'set_main_window: No old MainWindow found'
329                        ' and is_top_level is False;'
330                        ' this should not happen.'
331                    )
332                    window.main_window_back_state = None
333                else:
334                    window.main_window_back_state = self.save_main_window_state(
335                        oldwin
336                    )
337
338        self._main_window = window_weakref
339        self._main_window_widget = window_widget
340
341    def has_main_window(self) -> bool:
342        """Return whether a main menu window is present."""
343        return bool(self._main_window_widget)
344
345    def clear_main_window(self, transition: str | None = None) -> None:
346        """Clear any existing main window."""
347        from bauiv1._uitypes import MainWindow
348
349        main_window = self._main_window()
350        if main_window:
351            main_window.main_window_close(transition=transition)
352        else:
353            # Fallback; if we have a widget but no window, nuke the widget.
354            if self._main_window_widget:
355                logging.error(
356                    'Have _main_window_widget but no main_window'
357                    ' on clear_main_window; unexpected.'
358                )
359                self._main_window_widget.delete()
360
361        self._main_window = empty_weakref(MainWindow)
362        self._main_window_widget = None
363
364    def save_main_window_state(self, window: MainWindow) -> MainWindowState:
365        """Fully initialize a window-state from a window.
366
367        Use this to get a complete state for later restoration purposes.
368        Calling the window's get_main_window_state() directly is
369        insufficient.
370        """
371        winstate = window.get_main_window_state()
372
373        # Store some common window stuff on its state.
374        winstate.parent = window.main_window_back_state
375        winstate.is_top_level = window.main_window_is_top_level
376        winstate.is_auxiliary = window.main_window_is_auxiliary
377        winstate.window_type = type(window)
378
379        return winstate
380
381    def restore_main_window_state(self, state: MainWindowState) -> None:
382        """Restore UI to a saved state."""
383        existing = self.get_main_window()
384        if existing is not None:
385            raise RuntimeError('There is already a MainWindow.')
386
387        # Valid states should have a value here.
388        assert state.is_top_level is not None
389        assert state.is_auxiliary is not None
390        assert state.window_type is not None
391
392        win = state.create_window(transition=None)
393        self.set_main_window(
394            win,
395            from_window=False,  # disable check
396            is_top_level=state.is_top_level,
397            is_auxiliary=state.is_auxiliary,
398            back_state=state.parent,
399            suppress_warning=True,
400        )
401
402    @override
403    def on_ui_scale_change(self) -> None:
404        # Update our stored UIScale.
405        self._update_ui_scale()
406
407        # Update native bits (allow root widget to rebuild itself/etc.)
408        _bauiv1.on_ui_scale_change()
409
410        # Lastly, if we have a main window, recreate it to pick up the
411        # new UIScale/etc.
412        mainwindow = self.get_main_window()
413        if mainwindow is not None:
414            winstate = self.save_main_window_state(mainwindow)
415            self.clear_main_window(transition='instant')
416            self.restore_main_window_state(winstate)
417
418            # Store the size we created this for to avoid redundant
419            # future recreates.
420            self._last_win_recreate_size = babase.get_virtual_screen_size()
421
422    @override
423    def on_screen_size_change(self) -> None:
424
425        # HACK-ish: We currently ignore all resizes that happen while a
426        # string-edit is in progress. Otherwise the target text-widget
427        # of the edit generally dies during window recreates and the
428        # edit doesn't work. And it seems that in some cases on Android
429        # bringing up the on-screen keyboard results in the screen size
430        # changing due to nav-bars being shown or whatnot which makes
431        # the problem worse.
432        if babase.app.stringedit.active_adapter() is not None:
433            return
434
435        # Recreating a MainWindow is a kinda heavy thing and it doesn't
436        # seem like we should be doing it at 120hz during a live window
437        # resize, so let's limit the max rate we do it.
438        now = time.monotonic()
439
440        # Up to 4 refreshes per second seems reasonable.
441        interval = 0.25
442
443        # If there is a timer set already, do nothing.
444        if self._screen_size_win_recreate_timer is not None:
445            return
446
447        # Ok; there's no timer. Schedule one.
448        till_update = (
449            0.0
450            if self._last_screen_size_win_recreate_time is None
451            else max(
452                0.0, self._last_screen_size_win_recreate_time + interval - now
453            )
454        )
455        self._screen_size_win_recreate_timer = babase.AppTimer(
456            till_update, self._do_screen_size_win_recreate
457        )
458
459    def _do_screen_size_win_recreate(self) -> None:
460        self._last_screen_size_win_recreate_time = time.monotonic()
461        self._screen_size_win_recreate_timer = None
462
463        # Avoid recreating if we're already at this size. This prevents
464        # a redundant recreate when ui scale changes.
465        virtual_screen_size = babase.get_virtual_screen_size()
466        if virtual_screen_size == self._last_win_recreate_size:
467            return
468
469        mainwindow = self.get_main_window()
470        if (
471            mainwindow is not None
472            and mainwindow.refreshes_on_screen_size_changes
473        ):
474            winstate = self.save_main_window_state(mainwindow)
475            self.clear_main_window(transition='instant')
476            self.restore_main_window_state(winstate)
477
478            # Store the size we created this for to avoid redundant
479            # future recreates.
480            self._last_win_recreate_size = virtual_screen_size

Consolidated UI functionality for the app.

Category: App Classes

To use this class, access the single instance of it at 'ba.app.ui'.

quit_window: _bauiv1.Widget | None
window_states: dict[type, typing.Any]
cleanupchecks: list[bauiv1._uitypes.UICleanupCheck]
upkeeptimer: _babase.AppTimer | None
title_color
heading_color
infotextcolor
root_ui_calls: dict[UIV1AppSubsystem.RootUIElement, typing.Callable[[], NoneType]]
available: bool
112    @property
113    def available(self) -> bool:
114        """Can uiv1 currently be used?
115
116        Code that may run in headless mode, before the UI has been spun up,
117        while other ui systems are active, etc. can check this to avoid
118        likely erroring.
119        """
120        return _bauiv1.is_available()

Can uiv1 currently be used?

Code that may run in headless mode, before the UI has been spun up, while other ui systems are active, etc. can check this to avoid likely erroring.

@override
def reset(self) -> None:
122    @override
123    def reset(self) -> None:
124        from bauiv1._uitypes import MainWindow
125
126        self.root_ui_calls.clear()
127        self._main_window = empty_weakref(MainWindow)
128        self._main_window_widget = None

Reset the subsystem to a default state.

This is called when switching app modes, but may be called at other times too.

uiscale: UIScale
130    @property
131    def uiscale(self) -> babase.UIScale:
132        """Current ui scale for the app."""
133        return self._uiscale

Current ui scale for the app.

@override
def on_app_loading(self) -> None:
135    @override
136    def on_app_loading(self) -> None:
137        from bauiv1._uitypes import ui_upkeep
138
139        # IMPORTANT: If tweaking UI stuff, make sure it behaves for
140        # small, medium, and large UI modes. (doesn't run off screen,
141        # etc). The overrides below can be used to test with different
142        # sizes. Generally small is used on phones, medium is used on
143        # tablets/tvs, and large is on desktop computers or perhaps
144        # large tablets. When possible, run in windowed mode and resize
145        # the window to assure this holds true at all aspect ratios.
146
147        # UPDATE: A better way to test this is now by setting the
148        # environment variable BA_UI_SCALE to "small", "medium", or
149        # "large". This will affect system UIs not covered by the values
150        # below such as screen-messages. The below values remain
151        # functional, however, for cases such as Android where
152        # environment variables can't be set easily.
153
154        if bool(False):  # force-test ui scale
155            self._uiscale = babase.UIScale.SMALL
156            with babase.ContextRef.empty():
157                babase.pushcall(
158                    lambda: babase.screenmessage(
159                        f'FORCING UISCALE {self._uiscale.name} FOR TESTING',
160                        color=(1, 0, 1),
161                        log=True,
162                    )
163                )
164
165        # Kick off our periodic UI upkeep.
166
167        # FIXME: Can probably kill this if we do immediate UI death
168        # checks.
169        self.upkeeptimer = babase.AppTimer(2.6543, ui_upkeep, repeat=True)

Called when the app reaches the loading state.

Note that subsystems created after the app switches to the loading state will not receive this callback. Subsystems created by plugins are an example of this.

def get_main_window(self) -> MainWindow | None:
171    def get_main_window(self) -> bauiv1.MainWindow | None:
172        """Return main window, if any."""
173        return self._main_window()

Return main window, if any.

def set_main_window( self, window: MainWindow, *, from_window: MainWindow | None | bool = True, is_back: bool = False, is_top_level: bool = False, is_auxiliary: bool = False, back_state: MainWindowState | None = None, suppress_warning: bool = False) -> None:
175    def set_main_window(
176        self,
177        window: bauiv1.MainWindow,
178        *,
179        from_window: bauiv1.MainWindow | None | bool = True,
180        is_back: bool = False,
181        is_top_level: bool = False,
182        is_auxiliary: bool = False,
183        back_state: MainWindowState | None = None,
184        suppress_warning: bool = False,
185    ) -> None:
186        """Set the current 'main' window.
187
188        Generally this should not be called directly; The high level
189        MainWindow methods main_window_replace() and main_window_back()
190        should be used whenever possible to implement navigation.
191
192        The caller is responsible for cleaning up any previous main
193        window.
194        """
195        # pylint: disable=too-many-locals
196        # pylint: disable=too-many-branches
197        # pylint: disable=too-many-statements
198        from bauiv1._uitypes import MainWindow
199
200        # Encourage migration to the new higher level nav calls.
201        if not suppress_warning:
202            warnings.warn(
203                'set_main_window() should usually not be called directly;'
204                ' use the main_window_replace() or main_window_back()'
205                ' methods on MainWindow objects for navigation instead.'
206                ' If you truly need to use set_main_window(),'
207                ' pass suppress_warning=True to silence this warning.',
208                DeprecationWarning,
209                stacklevel=2,
210            )
211
212        # We used to accept Widgets but now want MainWindows.
213        if not isinstance(window, MainWindow):
214            raise RuntimeError(
215                f'set_main_window() now takes a MainWindow as its "window" arg.'
216                f' You passed a {type(window)}.',
217            )
218        window_weakref = weakref.ref(window)
219        window_widget = window.get_root_widget()
220
221        if not isinstance(from_window, MainWindow):
222            if from_window is not None and not isinstance(from_window, bool):
223                raise RuntimeError(
224                    f'set_main_window() now takes a MainWindow or bool or None'
225                    f'as its "from_window" arg.'
226                    f' You passed a {type(from_window)}.',
227                )
228
229        existing = self._main_window()
230
231        # If they passed a back-state, make sure it is fully filled out.
232        if back_state is not None:
233            if (
234                back_state.is_top_level is None
235                or back_state.is_auxiliary is None
236                or back_state.window_type is None
237            ):
238                raise RuntimeError(
239                    'Provided back_state is incomplete.'
240                    ' Make sure to only pass fully-filled-out MainWindowStates.'
241                )
242
243        # If a top-level main-window is being set, complain if there already
244        # is a main-window.
245        if is_top_level:
246            if existing:
247                logging.warning(
248                    'set_main_window() called with top-level window %s'
249                    ' but found existing main-window %s.',
250                    window,
251                    existing,
252                )
253        else:
254            # In other cases, sanity-check that the window asking for
255            # this switch is the one we're switching away from.
256            try:
257                if isinstance(from_window, bool):
258                    # For default val True we warn that the arg wasn't
259                    # passed. False can be explicitly passed to disable
260                    # this check.
261                    if from_window is True:
262                        caller_frame = inspect.stack()[1]
263                        caller_filename = caller_frame.filename
264                        caller_line_number = caller_frame.lineno
265                        logging.warning(
266                            'set_main_window() should be passed a'
267                            " 'from_window' value to help ensure proper"
268                            ' UI behavior (%s line %i).',
269                            caller_filename,
270                            caller_line_number,
271                        )
272                else:
273                    # For everything else, warn if what they passed
274                    # wasn't the previous main menu widget.
275                    if from_window is not existing:
276                        caller_frame = inspect.stack()[1]
277                        caller_filename = caller_frame.filename
278                        caller_line_number = caller_frame.lineno
279                        logging.warning(
280                            "set_main_window() was passed 'from_window' %s"
281                            ' but existing main-menu-window is %s.'
282                            ' (%s line %i).',
283                            from_window,
284                            existing,
285                            caller_filename,
286                            caller_line_number,
287                        )
288            except Exception:
289                # Prevent any bugs in these checks from causing problems.
290                logging.exception('Error checking from_window')
291
292        if is_back:
293            # These values should only be passed for forward navigation.
294            assert not is_top_level
295            assert not is_auxiliary
296            # Make sure back state is complete.
297            assert back_state is not None
298            assert back_state.is_top_level is not None
299            assert back_state.is_auxiliary is not None
300            assert back_state.window_type is type(window)
301            window.main_window_back_state = back_state.parent
302            window.main_window_is_top_level = back_state.is_top_level
303            window.main_window_is_auxiliary = back_state.is_auxiliary
304        else:
305            # Store if the window is top-level so we won't complain later if
306            # we go back from it and there's nowhere to go to.
307            window.main_window_is_top_level = is_top_level
308
309            window.main_window_is_auxiliary = is_auxiliary
310
311            # When navigating forward, generate a back-window-state from
312            # the outgoing window.
313            if is_top_level:
314                # Top level windows don't have or expect anywhere to
315                # go back to.
316                window.main_window_back_state = None
317            elif back_state is not None:
318                window.main_window_back_state = back_state
319            else:
320                oldwin = self._main_window()
321                if oldwin is None:
322                    # We currenty only hold weak refs to windows so that
323                    # they are free to die on their own, but we expect
324                    # the main menu window to keep itself alive as long
325                    # as its the main one. Holler if that seems to not
326                    # be happening.
327                    logging.warning(
328                        'set_main_window: No old MainWindow found'
329                        ' and is_top_level is False;'
330                        ' this should not happen.'
331                    )
332                    window.main_window_back_state = None
333                else:
334                    window.main_window_back_state = self.save_main_window_state(
335                        oldwin
336                    )
337
338        self._main_window = window_weakref
339        self._main_window_widget = window_widget

Set the current 'main' window.

Generally this should not be called directly; The high level MainWindow methods main_window_replace() and main_window_back() should be used whenever possible to implement navigation.

The caller is responsible for cleaning up any previous main window.

def has_main_window(self) -> bool:
341    def has_main_window(self) -> bool:
342        """Return whether a main menu window is present."""
343        return bool(self._main_window_widget)

Return whether a main menu window is present.

def clear_main_window(self, transition: str | None = None) -> None:
345    def clear_main_window(self, transition: str | None = None) -> None:
346        """Clear any existing main window."""
347        from bauiv1._uitypes import MainWindow
348
349        main_window = self._main_window()
350        if main_window:
351            main_window.main_window_close(transition=transition)
352        else:
353            # Fallback; if we have a widget but no window, nuke the widget.
354            if self._main_window_widget:
355                logging.error(
356                    'Have _main_window_widget but no main_window'
357                    ' on clear_main_window; unexpected.'
358                )
359                self._main_window_widget.delete()
360
361        self._main_window = empty_weakref(MainWindow)
362        self._main_window_widget = None

Clear any existing main window.

def save_main_window_state( self, window: MainWindow) -> MainWindowState:
364    def save_main_window_state(self, window: MainWindow) -> MainWindowState:
365        """Fully initialize a window-state from a window.
366
367        Use this to get a complete state for later restoration purposes.
368        Calling the window's get_main_window_state() directly is
369        insufficient.
370        """
371        winstate = window.get_main_window_state()
372
373        # Store some common window stuff on its state.
374        winstate.parent = window.main_window_back_state
375        winstate.is_top_level = window.main_window_is_top_level
376        winstate.is_auxiliary = window.main_window_is_auxiliary
377        winstate.window_type = type(window)
378
379        return winstate

Fully initialize a window-state from a window.

Use this to get a complete state for later restoration purposes. Calling the window's get_main_window_state() directly is insufficient.

def restore_main_window_state(self, state: MainWindowState) -> None:
381    def restore_main_window_state(self, state: MainWindowState) -> None:
382        """Restore UI to a saved state."""
383        existing = self.get_main_window()
384        if existing is not None:
385            raise RuntimeError('There is already a MainWindow.')
386
387        # Valid states should have a value here.
388        assert state.is_top_level is not None
389        assert state.is_auxiliary is not None
390        assert state.window_type is not None
391
392        win = state.create_window(transition=None)
393        self.set_main_window(
394            win,
395            from_window=False,  # disable check
396            is_top_level=state.is_top_level,
397            is_auxiliary=state.is_auxiliary,
398            back_state=state.parent,
399            suppress_warning=True,
400        )

Restore UI to a saved state.

@override
def on_ui_scale_change(self) -> None:
402    @override
403    def on_ui_scale_change(self) -> None:
404        # Update our stored UIScale.
405        self._update_ui_scale()
406
407        # Update native bits (allow root widget to rebuild itself/etc.)
408        _bauiv1.on_ui_scale_change()
409
410        # Lastly, if we have a main window, recreate it to pick up the
411        # new UIScale/etc.
412        mainwindow = self.get_main_window()
413        if mainwindow is not None:
414            winstate = self.save_main_window_state(mainwindow)
415            self.clear_main_window(transition='instant')
416            self.restore_main_window_state(winstate)
417
418            # Store the size we created this for to avoid redundant
419            # future recreates.
420            self._last_win_recreate_size = babase.get_virtual_screen_size()

Called when screen ui-scale changes.

Will not be called for the initial ui scale.

@override
def on_screen_size_change(self) -> None:
422    @override
423    def on_screen_size_change(self) -> None:
424
425        # HACK-ish: We currently ignore all resizes that happen while a
426        # string-edit is in progress. Otherwise the target text-widget
427        # of the edit generally dies during window recreates and the
428        # edit doesn't work. And it seems that in some cases on Android
429        # bringing up the on-screen keyboard results in the screen size
430        # changing due to nav-bars being shown or whatnot which makes
431        # the problem worse.
432        if babase.app.stringedit.active_adapter() is not None:
433            return
434
435        # Recreating a MainWindow is a kinda heavy thing and it doesn't
436        # seem like we should be doing it at 120hz during a live window
437        # resize, so let's limit the max rate we do it.
438        now = time.monotonic()
439
440        # Up to 4 refreshes per second seems reasonable.
441        interval = 0.25
442
443        # If there is a timer set already, do nothing.
444        if self._screen_size_win_recreate_timer is not None:
445            return
446
447        # Ok; there's no timer. Schedule one.
448        till_update = (
449            0.0
450            if self._last_screen_size_win_recreate_time is None
451            else max(
452                0.0, self._last_screen_size_win_recreate_time + interval - now
453            )
454        )
455        self._screen_size_win_recreate_timer = babase.AppTimer(
456            till_update, self._do_screen_size_win_recreate
457        )

Called when the screen size changes.

Will not be called for the initial screen size.

class UIV1AppSubsystem.RootUIElement(enum.Enum):
41    class RootUIElement(Enum):
42        """Stuff provided by the root ui."""
43
44        MENU_BUTTON = 'menu_button'
45        SQUAD_BUTTON = 'squad_button'
46        ACCOUNT_BUTTON = 'account_button'
47        SETTINGS_BUTTON = 'settings_button'
48        INBOX_BUTTON = 'inbox_button'
49        STORE_BUTTON = 'store_button'
50        INVENTORY_BUTTON = 'inventory_button'
51        ACHIEVEMENTS_BUTTON = 'achievements_button'
52        GET_TOKENS_BUTTON = 'get_tokens_button'
53        TICKETS_METER = 'tickets_meter'
54        TOKENS_METER = 'tokens_meter'
55        TROPHY_METER = 'trophy_meter'
56        LEVEL_METER = 'level_meter'
57        CHEST_SLOT_0 = 'chest_slot_0'
58        CHEST_SLOT_1 = 'chest_slot_1'
59        CHEST_SLOT_2 = 'chest_slot_2'
60        CHEST_SLOT_3 = 'chest_slot_3'

Stuff provided by the root ui.

MENU_BUTTON = <RootUIElement.MENU_BUTTON: 'menu_button'>
SQUAD_BUTTON = <RootUIElement.SQUAD_BUTTON: 'squad_button'>
ACCOUNT_BUTTON = <RootUIElement.ACCOUNT_BUTTON: 'account_button'>
SETTINGS_BUTTON = <RootUIElement.SETTINGS_BUTTON: 'settings_button'>
INBOX_BUTTON = <RootUIElement.INBOX_BUTTON: 'inbox_button'>
STORE_BUTTON = <RootUIElement.STORE_BUTTON: 'store_button'>
INVENTORY_BUTTON = <RootUIElement.INVENTORY_BUTTON: 'inventory_button'>
ACHIEVEMENTS_BUTTON = <RootUIElement.ACHIEVEMENTS_BUTTON: 'achievements_button'>
GET_TOKENS_BUTTON = <RootUIElement.GET_TOKENS_BUTTON: 'get_tokens_button'>
TICKETS_METER = <RootUIElement.TICKETS_METER: 'tickets_meter'>
TOKENS_METER = <RootUIElement.TOKENS_METER: 'tokens_meter'>
TROPHY_METER = <RootUIElement.TROPHY_METER: 'trophy_meter'>
LEVEL_METER = <RootUIElement.LEVEL_METER: 'level_meter'>
CHEST_SLOT_0 = <RootUIElement.CHEST_SLOT_0: 'chest_slot_0'>
CHEST_SLOT_1 = <RootUIElement.CHEST_SLOT_1: 'chest_slot_1'>
CHEST_SLOT_2 = <RootUIElement.CHEST_SLOT_2: 'chest_slot_2'>
CHEST_SLOT_3 = <RootUIElement.CHEST_SLOT_3: 'chest_slot_3'>
def utc_now_cloud() -> datetime.datetime:
29def utc_now_cloud() -> datetime.datetime:
30    """Returns estimated utc time regardless of local clock settings.
31
32    Applies offsets pulled from server communication/etc.
33    """
34    # TODO: wire this up. Just using local time for now. Make sure that
35    # BaseFeatureSet::TimeSinceEpochCloudSeconds() and this are synced
36    # up.
37    return utc_now()

Returns estimated utc time regardless of local clock settings.

Applies offsets pulled from server communication/etc.

WeakCall = <class 'babase._general._WeakCall'>
def widget( *, edit: _bauiv1.Widget, up_widget: _bauiv1.Widget | None = None, down_widget: _bauiv1.Widget | None = None, left_widget: _bauiv1.Widget | None = None, right_widget: _bauiv1.Widget | None = None, show_buffer_top: float | None = None, show_buffer_bottom: float | None = None, show_buffer_left: float | None = None, show_buffer_right: float | None = None, depth_range: tuple[float, float] | None = None, autoselect: bool | None = None) -> None:
630def widget(
631    *,
632    edit: bauiv1.Widget,
633    up_widget: bauiv1.Widget | None = None,
634    down_widget: bauiv1.Widget | None = None,
635    left_widget: bauiv1.Widget | None = None,
636    right_widget: bauiv1.Widget | None = None,
637    show_buffer_top: float | None = None,
638    show_buffer_bottom: float | None = None,
639    show_buffer_left: float | None = None,
640    show_buffer_right: float | None = None,
641    depth_range: tuple[float, float] | None = None,
642    autoselect: bool | None = None,
643) -> None:
644    """Edit common attributes of any widget.
645
646    Category: **User Interface Functions**
647
648    Unlike other UI calls, this can only be used to edit, not to create.
649    """
650    return None

Edit common attributes of any widget.

Category: User Interface Functions

Unlike other UI calls, this can only be used to edit, not to create.

class Widget:
 77class Widget:
 78    """Internal type for low level UI elements; buttons, windows, etc.
 79
 80    Category: **User Interface Classes**
 81
 82    This class represents a weak reference to a widget object
 83    in the internal C++ layer. Currently, functions such as
 84    bauiv1.buttonwidget() must be used to instantiate or edit these.
 85    """
 86
 87    transitioning_out: bool
 88    """Whether this widget is in the process of dying (read only).
 89
 90       It can be useful to check this on a window's root widget to
 91       prevent multiple window actions from firing simultaneously,
 92       potentially leaving the UI in a broken state."""
 93
 94    def __bool__(self) -> bool:
 95        """Support for bool evaluation."""
 96        return bool(True)  # Slight obfuscation.
 97
 98    def activate(self) -> None:
 99        """Activates a widget; the same as if it had been clicked."""
100        return None
101
102    def add_delete_callback(self, call: Callable) -> None:
103        """Add a call to be run immediately after this widget is destroyed."""
104        return None
105
106    def delete(self, ignore_missing: bool = True) -> None:
107        """Delete the Widget. Ignores already-deleted Widgets if ignore_missing
108        is True; otherwise an Exception is thrown.
109        """
110        return None
111
112    def exists(self) -> bool:
113        """Returns whether the Widget still exists.
114        Most functionality will fail on a nonexistent widget.
115
116        Note that you can also use the boolean operator for this same
117        functionality, so a statement such as "if mywidget" will do
118        the right thing both for Widget objects and values of None.
119        """
120        return bool()
121
122    def get_children(self) -> list[bauiv1.Widget]:
123        """Returns any child Widgets of this Widget."""
124        import bauiv1
125
126        return [bauiv1.Widget()]
127
128    def get_screen_space_center(self) -> tuple[float, float]:
129        """Returns the coords of the bauiv1.Widget center relative to the center
130        of the screen. This can be useful for placing pop-up windows and other
131        special cases.
132        """
133        return (0.0, 0.0)
134
135    def get_selected_child(self) -> bauiv1.Widget | None:
136        """Returns the selected child Widget or None if nothing is selected."""
137        import bauiv1
138
139        return bauiv1.Widget()
140
141    def get_widget_type(self) -> str:
142        """Return the internal type of the Widget as a string. Note that this
143        is different from the Python bauiv1.Widget type, which is the same for
144        all widgets.
145        """
146        return str()

Internal type for low level UI elements; buttons, windows, etc.

Category: User Interface Classes

This class represents a weak reference to a widget object in the internal C++ layer. Currently, functions such as bauiv1.buttonwidget() must be used to instantiate or edit these.

transitioning_out: bool

Whether this widget is in the process of dying (read only).

It can be useful to check this on a window's root widget to prevent multiple window actions from firing simultaneously, potentially leaving the UI in a broken state.

def activate(self) -> None:
 98    def activate(self) -> None:
 99        """Activates a widget; the same as if it had been clicked."""
100        return None

Activates a widget; the same as if it had been clicked.

def add_delete_callback(self, call: Callable) -> None:
102    def add_delete_callback(self, call: Callable) -> None:
103        """Add a call to be run immediately after this widget is destroyed."""
104        return None

Add a call to be run immediately after this widget is destroyed.

def delete(self, ignore_missing: bool = True) -> None:
106    def delete(self, ignore_missing: bool = True) -> None:
107        """Delete the Widget. Ignores already-deleted Widgets if ignore_missing
108        is True; otherwise an Exception is thrown.
109        """
110        return None

Delete the Widget. Ignores already-deleted Widgets if ignore_missing is True; otherwise an Exception is thrown.

def exists(self) -> bool:
112    def exists(self) -> bool:
113        """Returns whether the Widget still exists.
114        Most functionality will fail on a nonexistent widget.
115
116        Note that you can also use the boolean operator for this same
117        functionality, so a statement such as "if mywidget" will do
118        the right thing both for Widget objects and values of None.
119        """
120        return bool()

Returns whether the Widget still exists. Most functionality will fail on a nonexistent widget.

Note that you can also use the boolean operator for this same functionality, so a statement such as "if mywidget" will do the right thing both for Widget objects and values of None.

def get_children(self) -> list[_bauiv1.Widget]:
122    def get_children(self) -> list[bauiv1.Widget]:
123        """Returns any child Widgets of this Widget."""
124        import bauiv1
125
126        return [bauiv1.Widget()]

Returns any child Widgets of this Widget.

def get_screen_space_center(self) -> tuple[float, float]:
128    def get_screen_space_center(self) -> tuple[float, float]:
129        """Returns the coords of the bauiv1.Widget center relative to the center
130        of the screen. This can be useful for placing pop-up windows and other
131        special cases.
132        """
133        return (0.0, 0.0)

Returns the coords of the bauiv1.Widget center relative to the center of the screen. This can be useful for placing pop-up windows and other special cases.

def get_selected_child(self) -> _bauiv1.Widget | None:
135    def get_selected_child(self) -> bauiv1.Widget | None:
136        """Returns the selected child Widget or None if nothing is selected."""
137        import bauiv1
138
139        return bauiv1.Widget()

Returns the selected child Widget or None if nothing is selected.

def get_widget_type(self) -> str:
141    def get_widget_type(self) -> str:
142        """Return the internal type of the Widget as a string. Note that this
143        is different from the Python bauiv1.Widget type, which is the same for
144        all widgets.
145        """
146        return str()

Return the internal type of the Widget as a string. Note that this is different from the Python bauiv1.Widget type, which is the same for all widgets.

class Window:
28class Window:
29    """A basic window.
30
31    Category: User Interface Classes
32
33    Essentially wraps a ContainerWidget with some higher level
34    functionality.
35    """
36
37    def __init__(self, root_widget: bauiv1.Widget, cleanupcheck: bool = True):
38        self._root_widget = root_widget
39
40        # Complain if we outlive our root widget.
41        if cleanupcheck:
42            uicleanupcheck(self, root_widget)
43
44    def get_root_widget(self) -> bauiv1.Widget:
45        """Return the root widget."""
46        return self._root_widget

A basic window.

Category: User Interface Classes

Essentially wraps a ContainerWidget with some higher level functionality.

Window(root_widget: _bauiv1.Widget, cleanupcheck: bool = True)
37    def __init__(self, root_widget: bauiv1.Widget, cleanupcheck: bool = True):
38        self._root_widget = root_widget
39
40        # Complain if we outlive our root widget.
41        if cleanupcheck:
42            uicleanupcheck(self, root_widget)
def get_root_widget(self) -> _bauiv1.Widget:
44    def get_root_widget(self) -> bauiv1.Widget:
45        """Return the root widget."""
46        return self._root_widget

Return the root widget.