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 8
  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    fade_screen,
 50    get_display_resolution,
 51    get_input_idle_time,
 52    get_ip_address_type,
 53    get_low_level_config_value,
 54    get_max_graphics_quality,
 55    get_remote_app_name,
 56    get_replays_dir,
 57    get_string_height,
 58    get_string_width,
 59    get_type_name,
 60    getclass,
 61    have_permission,
 62    in_logic_thread,
 63    increment_analytics_count,
 64    is_browser_likely_available,
 65    is_xcode_build,
 66    lock_all_input,
 67    LoginAdapter,
 68    LoginInfo,
 69    Lstr,
 70    native_review_request,
 71    native_review_request_supported,
 72    NotFoundError,
 73    open_file_externally,
 74    open_url,
 75    overlay_web_browser_close,
 76    overlay_web_browser_is_open,
 77    overlay_web_browser_is_supported,
 78    overlay_web_browser_open_url,
 79    Permission,
 80    Plugin,
 81    PluginSpec,
 82    pushcall,
 83    quit,
 84    QuitType,
 85    request_permission,
 86    safecolor,
 87    screenmessage,
 88    set_analytics_screen,
 89    set_low_level_config_value,
 90    set_ui_input_device,
 91    SpecialChar,
 92    supports_max_fps,
 93    supports_vsync,
 94    timestring,
 95    UIScale,
 96    unlock_all_input,
 97    WeakCall,
 98    workspaces_in_use,
 99)
100
101from _bauiv1 import (
102    buttonwidget,
103    checkboxwidget,
104    columnwidget,
105    containerwidget,
106    get_qrcode_texture,
107    get_special_widget,
108    getmesh,
109    getsound,
110    gettexture,
111    hscrollwidget,
112    imagewidget,
113    is_party_icon_visible,
114    Mesh,
115    rowwidget,
116    scrollwidget,
117    set_party_window_open,
118    Sound,
119    Texture,
120    textwidget,
121    uibounds,
122    Widget,
123    widget,
124)
125from bauiv1._keyboard import Keyboard
126from bauiv1._uitypes import (
127    Window,
128    MainWindowState,
129    BasicMainWindowState,
130    uicleanupcheck,
131    MainWindow,
132)
133from bauiv1._appsubsystem import UIV1AppSubsystem
134
135__all__ = [
136    'add_clean_frame_callback',
137    'allows_ticket_sales',
138    'app',
139    'AppIntent',
140    'AppIntentDefault',
141    'AppIntentExec',
142    'AppMode',
143    'appname',
144    'appnameupper',
145    'appnameupper',
146    'apptime',
147    'AppTime',
148    'apptimer',
149    'AppTimer',
150    'BasicMainWindowState',
151    'buttonwidget',
152    'Call',
153    'fullscreen_control_available',
154    'fullscreen_control_get',
155    'fullscreen_control_key_shortcut',
156    'fullscreen_control_set',
157    'charstr',
158    'checkboxwidget',
159    'clipboard_is_supported',
160    'clipboard_set_text',
161    'columnwidget',
162    'commit_app_config',
163    'containerwidget',
164    'ContextRef',
165    'displaytime',
166    'DisplayTime',
167    'displaytimer',
168    'DisplayTimer',
169    'do_once',
170    'fade_screen',
171    'get_display_resolution',
172    'get_input_idle_time',
173    'get_ip_address_type',
174    'get_low_level_config_value',
175    'get_max_graphics_quality',
176    'get_qrcode_texture',
177    'get_remote_app_name',
178    'get_replays_dir',
179    'get_special_widget',
180    'get_string_height',
181    'get_string_width',
182    'get_type_name',
183    'getclass',
184    'getmesh',
185    'getsound',
186    'gettexture',
187    'have_permission',
188    'hscrollwidget',
189    'imagewidget',
190    'in_logic_thread',
191    'increment_analytics_count',
192    'is_browser_likely_available',
193    'is_party_icon_visible',
194    'is_xcode_build',
195    'Keyboard',
196    'lock_all_input',
197    'LoginAdapter',
198    'LoginInfo',
199    'Lstr',
200    'MainWindow',
201    'MainWindowState',
202    'Mesh',
203    'native_review_request',
204    'native_review_request_supported',
205    'NotFoundError',
206    'open_file_externally',
207    'open_url',
208    'overlay_web_browser_close',
209    'overlay_web_browser_is_open',
210    'overlay_web_browser_is_supported',
211    'overlay_web_browser_open_url',
212    'Permission',
213    'Plugin',
214    'PluginSpec',
215    'pushcall',
216    'quit',
217    'QuitType',
218    'request_permission',
219    'rowwidget',
220    'safecolor',
221    'screenmessage',
222    'scrollwidget',
223    'set_analytics_screen',
224    'set_low_level_config_value',
225    'set_party_window_open',
226    'set_ui_input_device',
227    'Sound',
228    'SpecialChar',
229    'supports_max_fps',
230    'supports_vsync',
231    'Texture',
232    'textwidget',
233    'timestring',
234    'uibounds',
235    'uicleanupcheck',
236    'UIScale',
237    'UIV1AppSubsystem',
238    'unlock_all_input',
239    'WeakCall',
240    'widget',
241    'Widget',
242    'Window',
243    'workspaces_in_use',
244]
245
246# We want stuff to show up as bauiv1.Foo instead of bauiv1._sub.Foo.
247set_canonical_module_names(globals())
248
249# Sanity check: we want to keep ballistica's dependencies and
250# bootstrapping order clearly defined; let's check a few particular
251# modules to make sure they never directly or indirectly import us
252# before their own execs complete.
253if __debug__:
254    for _mdl in 'babase', '_babase':
255        if not hasattr(__import__(_mdl), '_REACHED_END_OF_MODULE'):
256            logging.warning(
257                '%s was imported before %s finished importing;'
258                ' should not happen.',
259                __name__,
260                _mdl,
261            )
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 _supports_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.
36        return cls._supports_intent(intent)
37
38    @classmethod
39    def _supports_intent(cls, intent: AppIntent) -> bool:
40        """Return whether our mode can handle the provided intent.
41
42        AppModes should override this to define what they can handle.
43        Note that AppExperience does not have to be considered here; that
44        is handled automatically by the can_handle_intent() call."""
45        raise NotImplementedError('AppMode subclasses must override this.')
46
47    def handle_intent(self, intent: AppIntent) -> None:
48        """Handle an intent."""
49        raise NotImplementedError('AppMode subclasses must override this.')
50
51    def on_activate(self) -> None:
52        """Called when the mode is being activated."""
53
54    def on_deactivate(self) -> None:
55        """Called when the mode is being deactivated."""
56
57    def on_app_active_changed(self) -> None:
58        """Called when babase.app.active changes.
59
60        The app-mode may want to take action such as pausing a running
61        game in such cases.
62        """

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 _supports_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.
36        return cls._supports_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 _supports_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:
47    def handle_intent(self, intent: AppIntent) -> None:
48        """Handle an intent."""
49        raise NotImplementedError('AppMode subclasses must override this.')

Handle an intent.

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

Called when the mode is being activated.

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

Called when the mode is being deactivated.

def on_app_active_changed(self) -> None:
57    def on_app_active_changed(self) -> None:
58        """Called when babase.app.active changes.
59
60        The app-mode may want to take action such as pausing a running
61        game in such cases.
62        """

Called when babase.app.active changes.

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

def apptime() -> AppTime:
552def apptime() -> babase.AppTime:
553    """Return the current app-time in seconds.
554
555    Category: **General Utility Functions**
556
557    App-time is a monotonic time value; it starts at 0.0 when the app
558    launches and will never jump by large amounts or go backwards, even if
559    the system time changes. Its progression will pause when the app is in
560    a suspended state.
561
562    Note that the AppTime returned here is simply float; it just has a
563    unique type in the type-checker's eyes to help prevent it from being
564    accidentally used with time functionality expecting other time types.
565    """
566    import babase  # pylint: disable=cyclic-import
567
568    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:
571def apptimer(time: float, call: Callable[[], Any]) -> None:
572    """Schedule a callable object to run based on app-time.
573
574    Category: **General Utility Functions**
575
576    This function creates a one-off timer which cannot be canceled or
577    modified once created. If you require the ability to do so, or need
578    a repeating timer, use the babase.AppTimer class instead.
579
580    ##### Arguments
581    ###### time (float)
582    > Length of time in seconds that the timer will wait before firing.
583
584    ###### call (Callable[[], Any])
585    > A callable Python object. Note that the timer will retain a
586    strong reference to the callable for as long as the timer exists, so you
587    may want to look into concepts such as babase.WeakCall if that is not
588    desired.
589
590    ##### Examples
591    Print some stuff through time:
592    >>> babase.screenmessage('hello from now!')
593    >>> babase.apptimer(1.0, babase.Call(babase.screenmessage,
594                              'hello from the future!'))
595    >>> babase.apptimer(2.0, babase.Call(babase.screenmessage,
596    ...                       'hello from the future 2!'))
597    """
598    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 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 WeakCall if that is not desired.

Examples

Print some stuff through time:

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

AppTimer(time: float, call: Callable[[], Any], repeat: bool = False)
93    def __init__(
94        self, time: float, call: Callable[[], Any], repeat: bool = False
95    ) -> None:
96        pass
class BasicMainWindowState(bauiv1.MainWindowState):
210class BasicMainWindowState(MainWindowState):
211    """A basic MainWindowState holding a lambda to recreate a MainWindow."""
212
213    def __init__(
214        self,
215        create_call: Callable[
216            [
217                Literal['in_right', 'in_left', 'in_scale'] | None,
218                bauiv1.Widget | None,
219            ],
220            bauiv1.MainWindow,
221        ],
222    ) -> None:
223        super().__init__()
224        self.create_call = create_call
225
226    @override
227    def create_window(
228        self,
229        transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
230        origin_widget: bauiv1.Widget | None = None,
231    ) -> bauiv1.MainWindow:
232        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])
213    def __init__(
214        self,
215        create_call: Callable[
216            [
217                Literal['in_right', 'in_left', 'in_scale'] | None,
218                bauiv1.Widget | None,
219            ],
220            bauiv1.MainWindow,
221        ],
222    ) -> None:
223        super().__init__()
224        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:
226    @override
227    def create_window(
228        self,
229        transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
230        origin_widget: bauiv1.Widget | None = None,
231    ) -> bauiv1.MainWindow:
232        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.

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

Create or edit a button widget.

Category: User Interface Functions

Pass a valid existing 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:
621def charstr(char_id: babase.SpecialChar) -> str:
622    """Get a unicode string representing a special character.
623
624    Category: **General Utility Functions**
625
626    Note that these utilize the private-use block of unicode characters
627    (U+E000-U+F8FF) and are specific to the game; exporting or rendering
628    them elsewhere will be meaningless.
629
630    See babase.SpecialChar for the list of available characters.
631    """
632    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 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:
199def checkboxwidget(
200    edit: bauiv1.Widget | None = None,
201    parent: bauiv1.Widget | None = None,
202    size: Sequence[float] | None = None,
203    position: Sequence[float] | None = None,
204    text: str | bauiv1.Lstr | None = None,
205    value: bool | None = None,
206    on_value_change_call: Callable[[bool], None] | None = None,
207    on_select_call: Callable[[], None] | None = None,
208    text_scale: float | None = None,
209    textcolor: Sequence[float] | None = None,
210    scale: float | None = None,
211    is_radio_button: bool | None = None,
212    maxwidth: float | None = None,
213    autoselect: bool | None = None,
214    color: Sequence[float] | None = None,
215) -> bauiv1.Widget:
216    """Create or edit a check-box widget.
217
218    Category: **User Interface Functions**
219
220    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
221    a new one is created and returned. Arguments that are not set to None
222    are applied to the Widget.
223    """
224    import bauiv1  # pylint: disable=cyclic-import
225
226    return bauiv1.Widget()

Create or edit a check-box widget.

Category: User Interface Functions

Pass a valid existing 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:
657def clipboard_is_supported() -> bool:
658    """Return whether this platform supports clipboard operations at all.
659
660    Category: **General Utility Functions**
661
662    If this returns False, UIs should not show 'copy to clipboard'
663    buttons, etc.
664    """
665    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:
668def clipboard_set_text(value: str) -> None:
669    """Copy a string to the system clipboard.
670
671    Category: **General Utility Functions**
672
673    Ensure that babase.clipboard_is_supported() returns True before adding
674     buttons/etc. that make use of this functionality.
675    """
676    return None

Copy a string to the system clipboard.

Category: General Utility Functions

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

Create or edit a column widget.

Category: User Interface Functions

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

Create or edit a container widget.

Category: User Interface Functions

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

Whether the context was created as empty.

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

Whether the context has expired.

def displaytime() -> DisplayTime:
761def displaytime() -> babase.DisplayTime:
762    """Return the current display-time in seconds.
763
764    Category: **General Utility Functions**
765
766    Display-time is a time value intended to be used for animation and other
767    visual purposes. It will generally increment by a consistent amount each
768    frame. It will pass at an overall similar rate to AppTime, but trades
769    accuracy for smoothness.
770
771    Note that the value returned here is simply a float; it just has a
772    unique type in the type-checker's eyes to help prevent it from being
773    accidentally used with time functionality expecting other time types.
774    """
775    import babase  # pylint: disable=cyclic-import
776
777    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:
780def displaytimer(time: float, call: Callable[[], Any]) -> None:
781    """Schedule a callable object to run based on display-time.
782
783    Category: **General Utility Functions**
784
785    This function creates a one-off timer which cannot be canceled or
786    modified once created. If you require the ability to do so, or need
787    a repeating timer, use the babase.DisplayTimer class instead.
788
789    Display-time is a time value intended to be used for animation and other
790    visual purposes. It will generally increment by a consistent amount each
791    frame. It will pass at an overall similar rate to AppTime, but trades
792    accuracy for smoothness.
793
794    ##### Arguments
795    ###### time (float)
796    > Length of time in seconds that the timer will wait before firing.
797
798    ###### call (Callable[[], Any])
799    > A callable Python object. Note that the timer will retain a
800    strong reference to the callable for as long as the timer exists, so you
801    may want to look into concepts such as babase.WeakCall if that is not
802    desired.
803
804    ##### Examples
805    Print some stuff through time:
806    >>> babase.screenmessage('hello from now!')
807    >>> babase.displaytimer(1.0, babase.Call(babase.screenmessage,
808    ...                       'hello from the future!'))
809    >>> babase.displaytimer(2.0, babase.Call(babase.screenmessage,
810    ...                       'hello from the future 2!'))
811    """
812    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 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 WeakCall if that is not desired.

Examples

Print some stuff through time:

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

DisplayTimer(time: float, call: Callable[[], Any], repeat: bool = False)
265    def __init__(
266        self, time: float, call: Callable[[], Any], repeat: bool = False
267    ) -> None:
268        pass
def do_once() -> bool:
820def do_once() -> bool:
821    """Return whether this is the first time running a line of code.
822
823    Category: **General Utility Functions**
824
825    This is used by 'print_once()' type calls to keep from overflowing
826    logs. The call functions by registering the filename and line where
827    The call is made from.  Returns True if this location has not been
828    registered already, and False if it has.
829
830    ##### Example
831    This print will only fire for the first loop iteration:
832    >>> for i in range(10):
833    ... if babase.do_once():
834    ...     print('HelloWorld once from loop!')
835    """
836    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 do_once():
...     print('HelloWorld once from loop!')
def get_input_idle_time() -> float:
1000def get_input_idle_time() -> float:
1001    """Return seconds since any local input occurred (touch, keypress, etc.)."""
1002    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:
321def get_qrcode_texture(url: str) -> bauiv1.Texture:
322    """Return a QR code texture.
323
324    The provided url must be 64 bytes or less.
325    """
326    import bauiv1  # pylint: disable=cyclic-import
327
328    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:
355def getmesh(name: str) -> bauiv1.Mesh:
356    """Load a mesh for use solely in the local user interface."""
357    import bauiv1  # pylint: disable=cyclic-import
358
359    return bauiv1.Mesh()

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

def getsound(name: str) -> _bauiv1.Sound:
362def getsound(name: str) -> bauiv1.Sound:
363    """Load a sound for use in the ui."""
364    import bauiv1  # pylint: disable=cyclic-import
365
366    return bauiv1.Sound()

Load a sound for use in the ui.

def gettexture(name: str) -> _bauiv1.Texture:
369def gettexture(name: str) -> bauiv1.Texture:
370    """Load a texture for use in the ui."""
371    import bauiv1  # pylint: disable=cyclic-import
372
373    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, claims_tab: bool | None = None) -> _bauiv1.Widget:
376def hscrollwidget(
377    edit: bauiv1.Widget | None = None,
378    parent: bauiv1.Widget | None = None,
379    size: Sequence[float] | None = None,
380    position: Sequence[float] | None = None,
381    background: bool | None = None,
382    selected_child: bauiv1.Widget | None = None,
383    capture_arrows: bool | None = None,
384    on_select_call: Callable[[], None] | None = None,
385    center_small_content: bool | None = None,
386    color: Sequence[float] | None = None,
387    highlight: bool | None = None,
388    border_opacity: float | None = None,
389    simple_culling_h: float | None = None,
390    claims_left_right: bool | None = None,
391    claims_up_down: bool | None = None,
392    claims_tab: bool | None = None,
393) -> bauiv1.Widget:
394    """Create or edit a horizontal scroll widget.
395
396    Category: **User Interface Functions**
397
398    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
399    a new one is created and returned. Arguments that are not set to None
400    are applied to the Widget.
401    """
402    import bauiv1  # pylint: disable=cyclic-import
403
404    return bauiv1.Widget()

Create or edit a horizontal scroll widget.

Category: User Interface Functions

Pass a valid existing 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:
407def imagewidget(
408    edit: bauiv1.Widget | None = None,
409    parent: bauiv1.Widget | None = None,
410    size: Sequence[float] | None = None,
411    position: Sequence[float] | None = None,
412    color: Sequence[float] | None = None,
413    texture: bauiv1.Texture | None = None,
414    opacity: float | None = None,
415    mesh_transparent: bauiv1.Mesh | None = None,
416    mesh_opaque: bauiv1.Mesh | None = None,
417    has_alpha_channel: bool = True,
418    tint_texture: bauiv1.Texture | None = None,
419    tint_color: Sequence[float] | None = None,
420    transition_delay: float | None = None,
421    draw_controller: bauiv1.Widget | None = None,
422    tint2_color: Sequence[float] | None = None,
423    tilt_scale: float | None = None,
424    mask_texture: bauiv1.Texture | None = None,
425    radial_amount: float | None = None,
426    draw_controller_mult: float | None = None,
427) -> bauiv1.Widget:
428    """Create or edit an image widget.
429
430    Category: **User Interface Functions**
431
432    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
433    a new one is created and returned. Arguments that are not set to None
434    are applied to the Widget.
435    """
436    import bauiv1  # pylint: disable=cyclic-import
437
438    return bauiv1.Widget()

Create or edit an image widget.

Category: User Interface Functions

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

Describes the final result of a sign-in attempt.

LoginAdapter.SignInResult(credentials: str)
credentials: str
@dataclass
class LoginAdapter.ImplicitLoginState:
50    @dataclass
51    class ImplicitLoginState:
52        """Describes the current state of an implicit login."""
53
54        login_id: str
55        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:
25@dataclass
26class LoginInfo:
27    """Basic info about a login available in the app.plus.accounts section."""
28
29    name: str

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

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

EXAMPLE 4: 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 = Lstr(
...     resource='res_a',
...     subs=[('${NAME}', Lstr(resource='res_b'))])
Lstr(*args: Any, **keywds: Any)
558    def __init__(self, *args: Any, **keywds: Any) -> None:
559        """Instantiate a Lstr.
560
561        Pass a value for either 'resource', 'translate',
562        or 'value'. (see Lstr help for examples).
563        'subs' can be a sequence of 2-member sequences consisting of values
564        and replacements.
565        'fallback_resource' can be a resource key that will be used if the
566        main one is not present for
567        the current language in place of falling back to the english value
568        ('resource' mode only).
569        'fallback_value' can be a literal string that will be used if neither
570        the resource nor the fallback resource is found ('resource' mode only).
571        """
572        # pylint: disable=too-many-branches
573        if args:
574            raise TypeError('Lstr accepts only keyword arguments')
575
576        # Basically just store the exact args they passed.
577        # However if they passed any Lstr values for subs,
578        # replace them with that Lstr's dict.
579        self.args = keywds
580        our_type = type(self)
581
582        if isinstance(self.args.get('value'), our_type):
583            raise TypeError("'value' must be a regular string; not an Lstr")
584
585        if 'subs' in self.args:
586            subs_new = []
587            for key, value in keywds['subs']:
588                if isinstance(value, our_type):
589                    subs_new.append((key, value.args))
590                else:
591                    subs_new.append((key, value))
592            self.args['subs'] = subs_new
593
594        # As of protocol 31 we support compact key names
595        # ('t' instead of 'translate', etc). Convert as needed.
596        if 'translate' in keywds:
597            keywds['t'] = keywds['translate']
598            del keywds['translate']
599        if 'resource' in keywds:
600            keywds['r'] = keywds['resource']
601            del keywds['resource']
602        if 'value' in keywds:
603            keywds['v'] = keywds['value']
604            del keywds['value']
605        if 'fallback' in keywds:
606            from babase import _error
607
608            _error.print_error(
609                'deprecated "fallback" arg passed to Lstr(); use '
610                'either "fallback_resource" or "fallback_value"',
611                once=True,
612            )
613            keywds['f'] = keywds['fallback']
614            del keywds['fallback']
615        if 'fallback_resource' in keywds:
616            keywds['f'] = keywds['fallback_resource']
617            del keywds['fallback_resource']
618        if 'subs' in keywds:
619            keywds['s'] = keywds['subs']
620            del keywds['subs']
621        if 'fallback_value' in keywds:
622            keywds['fv'] = keywds['fallback_value']
623            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:
625    def evaluate(self) -> str:
626        """Evaluate the Lstr and returns a flat string in the current language.
627
628        You should avoid doing this as much as possible and instead pass
629        and store Lstr values.
630        """
631        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:
633    def is_flat_value(self) -> bool:
634        """Return whether the Lstr is a 'flat' value.
635
636        This is defined as a simple string value incorporating no
637        translations, resources, or substitutions. In this case it may
638        be reasonable to replace it with a raw string value, perform
639        string manipulation on it, etc.
640        """
641        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:
660    @staticmethod
661    def from_json(json_string: str) -> babase.Lstr:
662        """Given a json string, returns a babase.Lstr. Does no validation."""
663        lstr = Lstr(value='')
664        lstr.args = json.loads(json_string)
665        return lstr

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

class MainWindow(bauiv1.Window):
 49class MainWindow(Window):
 50    """A special window that can be used as a main window."""
 51
 52    def __init__(
 53        self,
 54        root_widget: bauiv1.Widget,
 55        transition: str | None,
 56        origin_widget: bauiv1.Widget | None,
 57        cleanupcheck: bool = True,
 58    ):
 59        """Create a MainWindow given a root widget and transition info.
 60
 61        Automatically handles in and out transitions on the provided widget,
 62        so there is no need to set transitions when creating it.
 63        """
 64        # TODO - move to MainWindow
 65        # A back-state supplied by the ui system.
 66        self.main_window_back_state: MainWindowState | None = None
 67
 68        self._main_window_transition = transition
 69        self._main_window_origin_widget = origin_widget
 70        super().__init__(root_widget, cleanupcheck)
 71
 72        scale_origin: tuple[float, float] | None
 73        if origin_widget is not None:
 74            self._main_window_transition_out = 'out_scale'
 75            scale_origin = origin_widget.get_screen_space_center()
 76            transition = 'in_scale'
 77        else:
 78            self._main_window_transition_out = 'out_right'
 79            scale_origin = None
 80        _bauiv1.containerwidget(
 81            edit=root_widget,
 82            transition=transition,
 83            scale_origin_stack_offset=scale_origin,
 84        )
 85
 86    def main_window_close(self) -> None:
 87        """Get window transitioning out if still alive."""
 88
 89        # no-op if our underlying widget is dead or on its way out.
 90        if not self._root_widget or self._root_widget.transitioning_out:
 91            return
 92
 93        # Transition ourself out.
 94        try:
 95            self.on_main_window_close()
 96        except Exception:
 97            logging.exception('Error in on_main_window_close() for %s.', self)
 98
 99        _bauiv1.containerwidget(
100            edit=self._root_widget, transition=self._main_window_transition_out
101        )
102
103    def can_change_main_window(self) -> bool:
104        """Is this MainWindow allowed to change the global main window?
105
106        It is a good idea to make sure this is True before calling
107        either main_window_back() or main_window_replace(). This
108        prevents fluke UI breakage such as multiple simultaneous events
109        causing a MainWindow to spawn multiple replacements for itself.
110        """
111        # We are allowed to change main windows if we are the current one
112        # AND our underlying widget is still alive and not transitioning out.
113        return (
114            babase.app.ui_v1.get_main_window() is self
115            and bool(self._root_widget)
116            and not self._root_widget.transitioning_out
117        )
118
119    def main_window_back(self) -> None:
120        """Move back in the main window stack."""
121
122        # Users should always check can_change_main_window() before
123        # calling us. Error if it seems they did not.
124        if not self.can_change_main_window():
125            raise RuntimeError(
126                'main_window_back() should only be called'
127                ' if can_change_main_window() returns True'
128                ' (it currently is False).'
129            )
130
131        # Get the 'back' window coming in.
132        babase.app.ui_v1.do_main_window_back(self)
133
134        self.main_window_close()
135
136    def main_window_replace(
137        self, new_window: MainWindow, group_id: str | None = None
138    ) -> None:
139        """Replace ourself with a new MainWindow."""
140
141        # Users should always check can_change_main_window() before
142        # creating new MainWindows and passing them in here. Error if it
143        # seems they did not.
144        if not self.can_change_main_window():
145            raise RuntimeError(
146                'main_window_replace() should only be called'
147                ' if can_change_main_window() returns True'
148                ' (it currently is False).'
149            )
150
151        # If we're navigating within a group, we want it to look like we're
152        # backing out of the old one and going into the new one.
153        if (
154            group_id is not None
155            and babase.app.ui_v1.main_window_group_id == group_id
156        ):
157            transition = self._main_window_transition_out
158        else:
159            # Otherwise just shove the old out the left to give the feel
160            # that we're adding to the nav stack.
161            transition = 'out_left'
162
163        # Transition ourself out.
164        try:
165            self.on_main_window_close()
166        except Exception:
167            logging.exception('Error in on_main_window_close() for %s.', self)
168
169        _bauiv1.containerwidget(edit=self._root_widget, transition=transition)
170        babase.app.ui_v1.set_main_window(
171            new_window, from_window=self, group_id=group_id
172        )
173
174    def on_main_window_close(self) -> None:
175        """Called before transitioning out a main window.
176
177        A good opportunity to save window state/etc.
178        """
179
180    def get_main_window_state(self) -> MainWindowState:
181        """Return a WindowState to recreate this window, if supported."""
182        # TODO - change to NotImplementedError when moved to MainWindow.
183        raise RuntimeError('FIXME NOT IMPLEMENTED')

A special window that can be used as a main window.

MainWindow( root_widget: _bauiv1.Widget, transition: str | None, origin_widget: _bauiv1.Widget | None, cleanupcheck: bool = True)
52    def __init__(
53        self,
54        root_widget: bauiv1.Widget,
55        transition: str | None,
56        origin_widget: bauiv1.Widget | None,
57        cleanupcheck: bool = True,
58    ):
59        """Create a MainWindow given a root widget and transition info.
60
61        Automatically handles in and out transitions on the provided widget,
62        so there is no need to set transitions when creating it.
63        """
64        # TODO - move to MainWindow
65        # A back-state supplied by the ui system.
66        self.main_window_back_state: MainWindowState | None = None
67
68        self._main_window_transition = transition
69        self._main_window_origin_widget = origin_widget
70        super().__init__(root_widget, cleanupcheck)
71
72        scale_origin: tuple[float, float] | None
73        if origin_widget is not None:
74            self._main_window_transition_out = 'out_scale'
75            scale_origin = origin_widget.get_screen_space_center()
76            transition = 'in_scale'
77        else:
78            self._main_window_transition_out = 'out_right'
79            scale_origin = None
80        _bauiv1.containerwidget(
81            edit=root_widget,
82            transition=transition,
83            scale_origin_stack_offset=scale_origin,
84        )

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
def main_window_close(self) -> None:
 86    def main_window_close(self) -> None:
 87        """Get window transitioning out if still alive."""
 88
 89        # no-op if our underlying widget is dead or on its way out.
 90        if not self._root_widget or self._root_widget.transitioning_out:
 91            return
 92
 93        # Transition ourself out.
 94        try:
 95            self.on_main_window_close()
 96        except Exception:
 97            logging.exception('Error in on_main_window_close() for %s.', self)
 98
 99        _bauiv1.containerwidget(
100            edit=self._root_widget, transition=self._main_window_transition_out
101        )

Get window transitioning out if still alive.

def can_change_main_window(self) -> bool:
103    def can_change_main_window(self) -> bool:
104        """Is this MainWindow allowed to change the global main window?
105
106        It is a good idea to make sure this is True before calling
107        either main_window_back() or main_window_replace(). This
108        prevents fluke UI breakage such as multiple simultaneous events
109        causing a MainWindow to spawn multiple replacements for itself.
110        """
111        # We are allowed to change main windows if we are the current one
112        # AND our underlying widget is still alive and not transitioning out.
113        return (
114            babase.app.ui_v1.get_main_window() is self
115            and bool(self._root_widget)
116            and not self._root_widget.transitioning_out
117        )

Is this MainWindow allowed to change the global main window?

It is a good idea to make sure this is True before calling either main_window_back() or 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:
119    def main_window_back(self) -> None:
120        """Move back in the main window stack."""
121
122        # Users should always check can_change_main_window() before
123        # calling us. Error if it seems they did not.
124        if not self.can_change_main_window():
125            raise RuntimeError(
126                'main_window_back() should only be called'
127                ' if can_change_main_window() returns True'
128                ' (it currently is False).'
129            )
130
131        # Get the 'back' window coming in.
132        babase.app.ui_v1.do_main_window_back(self)
133
134        self.main_window_close()

Move back in the main window stack.

def main_window_replace( self, new_window: MainWindow, group_id: str | None = None) -> None:
136    def main_window_replace(
137        self, new_window: MainWindow, group_id: str | None = None
138    ) -> None:
139        """Replace ourself with a new MainWindow."""
140
141        # Users should always check can_change_main_window() before
142        # creating new MainWindows and passing them in here. Error if it
143        # seems they did not.
144        if not self.can_change_main_window():
145            raise RuntimeError(
146                'main_window_replace() should only be called'
147                ' if can_change_main_window() returns True'
148                ' (it currently is False).'
149            )
150
151        # If we're navigating within a group, we want it to look like we're
152        # backing out of the old one and going into the new one.
153        if (
154            group_id is not None
155            and babase.app.ui_v1.main_window_group_id == group_id
156        ):
157            transition = self._main_window_transition_out
158        else:
159            # Otherwise just shove the old out the left to give the feel
160            # that we're adding to the nav stack.
161            transition = 'out_left'
162
163        # Transition ourself out.
164        try:
165            self.on_main_window_close()
166        except Exception:
167            logging.exception('Error in on_main_window_close() for %s.', self)
168
169        _bauiv1.containerwidget(edit=self._root_widget, transition=transition)
170        babase.app.ui_v1.set_main_window(
171            new_window, from_window=self, group_id=group_id
172        )

Replace ourself with a new MainWindow.

def on_main_window_close(self) -> None:
174    def on_main_window_close(self) -> None:
175        """Called before transitioning out a main window.
176
177        A good opportunity to save window state/etc.
178        """

Called before transitioning out a main window.

A good opportunity to save window state/etc.

def get_main_window_state(self) -> MainWindowState:
180    def get_main_window_state(self) -> MainWindowState:
181        """Return a WindowState to recreate this window, if supported."""
182        # TODO - change to NotImplementedError when moved to MainWindow.
183        raise RuntimeError('FIXME NOT IMPLEMENTED')

Return a WindowState to recreate this window, if supported.

Inherited Members
Window
get_root_widget
class MainWindowState:
186class MainWindowState:
187    """Persistent state for a specific main-window and its ancestors.
188
189    This allows MainWindows to be automatically recreated for back-button
190    purposes, when switching app-modes, etc.
191    """
192
193    def __init__(self, parent: MainWindowState | None = None) -> None:
194        # The window that back/cancel navigation should take us to.
195        self.parent = parent
196
197    def create_window(
198        self,
199        transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
200        origin_widget: bauiv1.Widget | None = None,
201    ) -> MainWindow:
202        """Create a window based on this state.
203
204        WindowState child classes should override this to recreate their
205        particular type of window.
206        """
207        raise NotImplementedError()

Persistent state for a specific main-window and its ancestors.

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

MainWindowState(parent: MainWindowState | None = None)
193    def __init__(self, parent: MainWindowState | None = None) -> None:
194        # The window that back/cancel navigation should take us to.
195        self.parent = parent
parent
def create_window( self, transition: Optional[Literal['in_right', 'in_left', 'in_scale']] = None, origin_widget: _bauiv1.Widget | None = None) -> MainWindow:
197    def create_window(
198        self,
199        transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
200        origin_widget: bauiv1.Widget | None = None,
201    ) -> MainWindow:
202        """Create a window based on this state.
203
204        WindowState child classes should override this to recreate their
205        particular type of window.
206        """
207        raise NotImplementedError()

Create a window based on this state.

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

class Mesh:
51class Mesh:
52    """Category: **User Interface Classes**"""
53
54    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

Inherited Members
builtins.Exception
Exception
builtins.BaseException
with_traceback
add_note
args
def open_url(address: str, force_fallback: bool = False) -> None:
1309def open_url(address: str, force_fallback: bool = False) -> None:
1310    """Open the provided URL.
1311
1312    Category: **General Utility Functions**
1313
1314    Attempts to open the provided url in a web-browser. If that is not
1315    possible (or force_fallback is True), instead displays the url as
1316    a string and/or qrcode.
1317    """
1318    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:
1321def overlay_web_browser_close() -> bool:
1322    """Close any open overlay web browser.
1323
1324    Category: **General Utility Functions**
1325    """
1326    return bool()

Close any open overlay web browser.

Category: General Utility Functions

def overlay_web_browser_is_open() -> bool:
1329def overlay_web_browser_is_open() -> bool:
1330    """Return whether an overlay web browser is open currently.
1331
1332    Category: **General Utility Functions**
1333    """
1334    return bool()

Return whether an overlay web browser is open currently.

Category: General Utility Functions

def overlay_web_browser_is_supported() -> bool:
1337def overlay_web_browser_is_supported() -> bool:
1338    """Return whether an overlay web browser is supported here.
1339
1340    Category: **General Utility Functions**
1341
1342    An overlay web browser is a small dialog that pops up over the top
1343    of the main engine window. It can be used for performing simple
1344    tasks such as sign-ins.
1345    """
1346    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:
1349def overlay_web_browser_open_url(address: str) -> None:
1350    """Open the provided URL in an overlayw web browser.
1351
1352    Category: **General Utility Functions**
1353
1354    An overlay web browser is a small dialog that pops up over the top
1355    of the main engine window. It can be used for performing simple
1356    tasks such as sign-ins.
1357    """
1358    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>
Inherited Members
enum.Enum
name
value
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:
1393def pushcall(
1394    call: Callable,
1395    from_other_thread: bool = False,
1396    suppress_other_thread_warning: bool = False,
1397    other_thread_use_fg_context: bool = False,
1398    raw: bool = False,
1399) -> None:
1400    """Push a call to the logic event-loop.
1401    Category: **General Utility Functions**
1402
1403    This call expects to be used in the logic thread, and will automatically
1404    save and restore the babase.Context to behave seamlessly.
1405
1406    If you want to push a call from outside of the logic thread,
1407    however, you can pass 'from_other_thread' as True. In this case
1408    the call will always run in the UI context_ref on the logic thread
1409    or whichever context_ref is in the foreground if
1410    other_thread_use_fg_context is True.
1411    Passing raw=True will disable thread checks and context_ref sets/restores.
1412    """
1413    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:
1417def quit(
1418    confirm: bool = False, quit_type: babase.QuitType | None = None
1419) -> None:
1420    """Quit the app.
1421
1422    Category: **General Utility Functions**
1423
1424    If 'confirm' is True, a confirm dialog will be presented if conditions
1425    allow; otherwise the quit will still be immediate.
1426    See docs for babase.QuitType for explanations of the optional
1427    'quit_type' arg.
1428    """
1429    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 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>
Inherited Members
enum.Enum
name
value
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, claims_tab: bool | None = None, selection_loops_to_parent: bool | None = None) -> _bauiv1.Widget:
456def rowwidget(
457    edit: bauiv1.Widget | None = None,
458    parent: bauiv1.Widget | None = None,
459    size: Sequence[float] | None = None,
460    position: Sequence[float] | None = None,
461    background: bool | None = None,
462    selected_child: bauiv1.Widget | None = None,
463    visible_child: bauiv1.Widget | None = None,
464    claims_left_right: bool | None = None,
465    claims_tab: bool | None = None,
466    selection_loops_to_parent: bool | None = None,
467) -> bauiv1.Widget:
468    """Create or edit a row widget.
469
470    Category: **User Interface Functions**
471
472    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
473    a new one is created and returned. Arguments that are not set to None
474    are applied to the Widget.
475    """
476    import bauiv1  # pylint: disable=cyclic-import
477
478    return bauiv1.Widget()

Create or edit a row widget.

Category: User Interface Functions

Pass a valid existing 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, ...]:
1467def safecolor(
1468    color: Sequence[float], target_intensity: float = 0.6
1469) -> tuple[float, ...]:
1470    """Given a color tuple, return a color safe to display as text.
1471
1472    Category: **General Utility Functions**
1473
1474    Accepts tuples of length 3 or 4. This will slightly brighten very
1475    dark colors, etc.
1476    """
1477    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:
1480def screenmessage(
1481    message: str | babase.Lstr,
1482    color: Sequence[float] | None = None,
1483    log: bool = False,
1484) -> None:
1485    """Print a message to the local client's screen, in a given color.
1486
1487    Category: **General Utility Functions**
1488
1489    Note that this version of the function is purely for local display.
1490    To broadcast screen messages in network play, look for methods such as
1491    broadcastmessage() provided by the scene-version packages.
1492    """
1493    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, 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, claims_tab: bool | None = None, autoselect: bool | None = None) -> _bauiv1.Widget:
481def scrollwidget(
482    edit: bauiv1.Widget | None = None,
483    parent: bauiv1.Widget | None = None,
484    size: Sequence[float] | None = None,
485    position: Sequence[float] | None = None,
486    background: bool | None = None,
487    selected_child: bauiv1.Widget | None = None,
488    capture_arrows: bool = False,
489    on_select_call: Callable | None = None,
490    center_small_content: bool | None = None,
491    color: Sequence[float] | None = None,
492    highlight: bool | None = None,
493    border_opacity: float | None = None,
494    simple_culling_v: float | None = None,
495    selection_loops_to_parent: bool | None = None,
496    claims_left_right: bool | None = None,
497    claims_up_down: bool | None = None,
498    claims_tab: bool | None = None,
499    autoselect: bool | None = None,
500) -> bauiv1.Widget:
501    """Create or edit a scroll widget.
502
503    Category: **User Interface Functions**
504
505    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
506    a new one is created and returned. Arguments that are not set to None
507    are applied to the Widget.
508    """
509    import bauiv1  # pylint: disable=cyclic-import
510
511    return bauiv1.Widget()

Create or edit a scroll widget.

Category: User Interface Functions

Pass a valid existing 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:
1496def set_analytics_screen(screen: str) -> None:
1497    """Used for analytics to see where in the app players spend their time.
1498
1499    Category: **General Utility Functions**
1500
1501    Generally called when opening a new window or entering some UI.
1502    'screen' should be a string description of an app location
1503    ('Main Menu', etc.)
1504    """
1505    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:
57class Sound:
58    """Category: **User Interface Classes**"""
59
60    def play(self) -> None:
61        """Play the sound locally."""
62        return None
63
64    def stop(self) -> None:
65        """Stop the sound if it is playing."""
66        return None

Category: User Interface Classes

def play(self) -> None:
60    def play(self) -> None:
61        """Play the sound locally."""
62        return None

Play the sound locally.

def stop(self) -> None:
64    def stop(self) -> None:
65        """Stop the sound if it is playing."""
66        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>
Inherited Members
enum.Enum
name
value
class Texture:
69class Texture:
70    """Category: **User Interface Classes**"""
71
72    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:
519def textwidget(
520    edit: bauiv1.Widget | None = None,
521    parent: bauiv1.Widget | None = None,
522    size: Sequence[float] | None = None,
523    position: Sequence[float] | None = None,
524    text: str | bauiv1.Lstr | None = None,
525    v_align: str | None = None,
526    h_align: str | None = None,
527    editable: bool | None = None,
528    padding: float | None = None,
529    on_return_press_call: Callable[[], None] | None = None,
530    on_activate_call: Callable[[], None] | None = None,
531    selectable: bool | None = None,
532    query: bauiv1.Widget | None = None,
533    max_chars: int | None = None,
534    color: Sequence[float] | None = None,
535    click_activate: bool | None = None,
536    on_select_call: Callable[[], None] | None = None,
537    always_highlight: bool | None = None,
538    draw_controller: bauiv1.Widget | None = None,
539    scale: float | None = None,
540    corner_scale: float | None = None,
541    description: str | bauiv1.Lstr | None = None,
542    transition_delay: float | None = None,
543    maxwidth: float | None = None,
544    max_height: float | None = None,
545    flatness: float | None = None,
546    shadow: float | None = None,
547    autoselect: bool | None = None,
548    rotate: float | None = None,
549    enabled: bool | None = None,
550    force_internal_editing: bool | None = None,
551    always_show_carat: bool | None = None,
552    big: bool | None = None,
553    extra_touch_border_scale: float | None = None,
554    res_scale: float | None = None,
555    query_max_chars: bauiv1.Widget | None = None,
556    query_description: bauiv1.Widget | None = None,
557    adapter_finished: bool | None = None,
558    glow_type: str | None = None,
559    allow_clear_button: bool | None = None,
560) -> bauiv1.Widget:
561    """Create or edit a text widget.
562
563    Category: **User Interface Functions**
564
565    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
566    a new one is created and returned. Arguments that are not set to None
567    are applied to the Widget.
568    """
569    import bauiv1  # pylint: disable=cyclic-import
570
571    return bauiv1.Widget()

Create or edit a text widget.

Category: User Interface Functions

Pass a valid existing 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 Lstr for displaying a time value.

Category: General Utility Functions

Given a time value, returns a 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:
244def uicleanupcheck(obj: Any, widget: bauiv1.Widget) -> None:
245    """Checks to ensure a widget-owning object gets cleaned up properly.
246
247    Category: User Interface Functions
248
249    This adds a check which will print an error message if the provided
250    object still exists ~5 seconds after the provided bauiv1.Widget dies.
251
252    This is a good sanity check for any sort of object that wraps or
253    controls a bauiv1.Widget. For instance, a 'Window' class instance has
254    no reason to still exist once its root container bauiv1.Widget has fully
255    transitioned out and been destroyed. Circular references or careless
256    strong referencing can lead to such objects never getting destroyed,
257    however, and this helps detect such cases to avoid memory leaks.
258    """
259    if DEBUG_UI_CLEANUP_CHECKS:
260        print(f'adding uicleanup to {obj}')
261    if not isinstance(widget, _bauiv1.Widget):
262        raise TypeError('widget arg is not a bauiv1.Widget')
263
264    if bool(False):
265
266        def foobar() -> None:
267            """Just testing."""
268            if DEBUG_UI_CLEANUP_CHECKS:
269                print('uicleanupcheck widget dying...')
270
271        widget.add_delete_callback(foobar)
272
273    assert babase.app.classic is not None
274    babase.app.ui_v1.cleanupchecks.append(
275        UICleanupCheck(
276            obj=weakref.ref(obj), widget=widget, widget_death_time=None
277        )
278    )

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 Widget dies.

This is a good sanity check for any sort of object that wraps or controls a Widget. For instance, a 'Window' class instance has no reason to still exist once its root container 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    LARGE = 0
85    MEDIUM = 1
86    SMALL = 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.

LARGE = <UIScale.LARGE: 0>
MEDIUM = <UIScale.MEDIUM: 1>
SMALL = <UIScale.SMALL: 2>
Inherited Members
enum.Enum
name
value
class UIV1AppSubsystem(babase._appsubsystem.AppSubsystem):
 31class UIV1AppSubsystem(babase.AppSubsystem):
 32    """Consolidated UI functionality for the app.
 33
 34    Category: **App Classes**
 35
 36    To use this class, access the single instance of it at 'ba.app.ui'.
 37    """
 38
 39    class RootUIElement(Enum):
 40        """Stuff provided by the root ui."""
 41
 42        MENU_BUTTON = 'menu_button'
 43        SQUAD_BUTTON = 'squad_button'
 44        ACCOUNT_BUTTON = 'account_button'
 45        SETTINGS_BUTTON = 'settings_button'
 46        INBOX_BUTTON = 'inbox_button'
 47        STORE_BUTTON = 'store_button'
 48        INVENTORY_BUTTON = 'inventory_button'
 49        ACHIEVEMENTS_BUTTON = 'achievements_button'
 50        GET_TOKENS_BUTTON = 'get_tokens_button'
 51        TICKETS_METER = 'tickets_meter'
 52        TOKENS_METER = 'tokens_meter'
 53        TROPHY_METER = 'trophy_meter'
 54        LEVEL_METER = 'level_meter'
 55        CHEST_SLOT_1 = 'chest_slot_1'
 56        CHEST_SLOT_2 = 'chest_slot_2'
 57        CHEST_SLOT_3 = 'chest_slot_3'
 58        CHEST_SLOT_4 = 'chest_slot_4'
 59
 60    def __init__(self) -> None:
 61        from bauiv1._uitypes import MainWindow
 62
 63        super().__init__()
 64        env = babase.env()
 65
 66        # We hold only a weak ref to the current main Window; we want it
 67        # to be able to disappear on its own. That being said, we do
 68        # expect MainWindows to keep themselves alive until replaced by
 69        # another MainWindow and we complain if they don't.
 70        self._main_window = empty_weakref(MainWindow)
 71        self._main_window_widget: bauiv1.Widget | None = None
 72        self.main_window_group_id: str | None = None
 73
 74        self.quit_window: bauiv1.Widget | None = None
 75
 76        # The following should probably go away or move to classic.
 77        # self._main_menu_location: str | None = None
 78
 79        # For storing arbitrary class-level state data for Windows or
 80        # other UI related classes.
 81        self.window_states: dict[type, Any] = {}
 82
 83        uiscalestr = babase.app.config.get('UI Scale', env['ui_scale'])
 84        if uiscalestr == 'auto':
 85            uiscalestr = env['ui_scale']
 86
 87        self._uiscale: babase.UIScale
 88        if uiscalestr == 'large':
 89            self._uiscale = babase.UIScale.LARGE
 90        elif uiscalestr == 'medium':
 91            self._uiscale = babase.UIScale.MEDIUM
 92        elif uiscalestr == 'small':
 93            self._uiscale = babase.UIScale.SMALL
 94        else:
 95            logging.error("Invalid UIScale '%s'.", uiscalestr)
 96            self._uiscale = babase.UIScale.MEDIUM
 97
 98        self.cleanupchecks: list[UICleanupCheck] = []
 99        self.upkeeptimer: babase.AppTimer | None = None
100
101        self.title_color = (0.72, 0.7, 0.75)
102        self.heading_color = (0.72, 0.7, 0.75)
103        self.infotextcolor = (0.7, 0.9, 0.7)
104
105        # Elements in our root UI will call anything here when activated.
106        self.root_ui_calls: dict[
107            UIV1AppSubsystem.RootUIElement, Callable[[], None]
108        ] = {}
109
110    @property
111    def available(self) -> bool:
112        """Can uiv1 currently be used?
113
114        Code that may run in headless mode, before the UI has been spun up,
115        while other ui systems are active, etc. can check this to avoid
116        likely erroring.
117        """
118        return _bauiv1.is_available()
119
120    @override
121    def reset(self) -> None:
122        from bauiv1._uitypes import MainWindow
123
124        self.root_ui_calls.clear()
125        self._main_window = empty_weakref(MainWindow)
126        self._main_window_widget = None
127        self.main_window_group_id = None
128
129    @property
130    def uiscale(self) -> babase.UIScale:
131        """Current ui scale for the app."""
132        return self._uiscale
133
134    @override
135    def on_app_loading(self) -> None:
136        from bauiv1._uitypes import ui_upkeep
137
138        # IMPORTANT: If tweaking UI stuff, make sure it behaves for small,
139        # medium, and large UI modes. (doesn't run off screen, etc).
140        # The overrides below can be used to test with different sizes.
141        # Generally small is used on phones, medium is used on tablets/tvs,
142        # and large is on desktop computers or perhaps large tablets. When
143        # possible, run in windowed mode and resize the window to assure
144        # this holds true at all aspect ratios.
145
146        # UPDATE: A better way to test this is now by setting the environment
147        # variable BA_UI_SCALE to "small", "medium", or "large".
148        # This will affect system UIs not covered by the values below such
149        # as screen-messages. The below values remain functional, however,
150        # for cases such as Android where environment variables can't be set
151        # easily.
152
153        if bool(False):  # force-test ui scale
154            self._uiscale = babase.UIScale.SMALL
155            with babase.ContextRef.empty():
156                babase.pushcall(
157                    lambda: babase.screenmessage(
158                        f'FORCING UISCALE {self._uiscale.name} FOR TESTING',
159                        color=(1, 0, 1),
160                        log=True,
161                    )
162                )
163
164        # Kick off our periodic UI upkeep.
165        # FIXME: Can probably kill this if we do immediate UI death checks.
166        self.upkeeptimer = babase.AppTimer(2.6543, ui_upkeep, repeat=True)
167
168    def do_main_window_back(self, window: MainWindow) -> None:
169        """Sets the main menu window automatically from a parent WindowState."""
170
171        main_window = self._main_window()
172        back_state = (
173            None if main_window is None else main_window.main_window_back_state
174        )
175        if back_state is None:
176            raise RuntimeError(
177                f'Main window {main_window} provides no back-state;'
178                f' cannot use auto-back.'
179            )
180        backwin = back_state.create_window(transition='in_left')
181        backwin.main_window_back_state = back_state.parent
182        self.set_main_window(backwin, from_window=window, is_back=True)
183
184    def get_main_window(self) -> bauiv1.MainWindow | None:
185        """Return main window, if any."""
186        return self._main_window()
187
188    def set_main_window(
189        self,
190        window: bauiv1.MainWindow,
191        from_window: bauiv1.MainWindow | None | bool = True,
192        is_back: bool = False,
193        group_id: str | None = None,
194        is_top_level: bool = False,
195        back_state: MainWindowState | None = None,
196    ) -> None:
197        """Set the current 'main' window, replacing any existing.
198
199        If 'from_window' is passed as a bauiv1.Widget or bauiv1.Window
200        or None, a warning will be issued if it that value does not
201        match the current main window. This can help identify flawed
202        code that can lead to bad UI states. A value of False will
203        disable the check, which is necessary in some cases when the
204        current main window is not known.
205
206        When navigating somewhere from a cancel or back-button, pass
207        is_back=True; this will prevent the new main window from itself
208        being registered as a new location on the stack that can be
209        returned to.
210
211        If a 'group_id' string is provided and the window being replaced
212        has the same group-id, the WindowState stack is left unchanged,
213        effectively replacing the previous window with the new one in
214        the stack. This can be useful in cases where tab-bar-like UIs
215        allow flipping between sibling windows with the back button
216        always leading to a shared parent.
217        """
218        # pylint: disable=too-many-locals
219        # pylint: disable=too-many-branches
220        # pylint: disable=too-many-statements
221        from bauiv1._uitypes import MainWindow
222
223        from_window_widget: bauiv1.Widget | None
224
225        # We used to accept Widgets but now want MainWindows.
226        assert isinstance(window, MainWindow)
227        window_weakref = weakref.ref(window)
228        window_widget = window.get_root_widget()
229
230        if isinstance(from_window, MainWindow):
231            from_window_widget = from_window.get_root_widget()
232        else:
233            from_window_widget = None
234
235        existing = self._main_window_widget
236
237        try:
238            if isinstance(from_window, bool):
239                # For default val True we warn that the arg wasn't
240                # passed. False can be explicitly passed to disable this
241                # check.
242                if from_window is True:
243                    caller_frame = inspect.stack()[1]
244                    caller_filename = caller_frame.filename
245                    caller_line_number = caller_frame.lineno
246                    logging.warning(
247                        'set_main_window() should be passed a'
248                        " 'from_window' value to help ensure proper UI behavior"
249                        ' (%s line %i).',
250                        caller_filename,
251                        caller_line_number,
252                    )
253            else:
254                # For everything else, warn if what they passed wasn't
255                # the previous main menu widget.
256                if from_window_widget is not existing:
257                    caller_frame = inspect.stack()[1]
258                    caller_filename = caller_frame.filename
259                    caller_line_number = caller_frame.lineno
260                    logging.warning(
261                        "set_main_window() was passed 'from_window' %s"
262                        ' but existing main-menu-window is %s. (%s line %i).',
263                        from_window_widget,
264                        existing,
265                        caller_filename,
266                        caller_line_number,
267                    )
268        except Exception:
269            # Prevent any bugs in these checks from causing problems.
270            logging.exception('Error checking from_window')
271
272        # Once the above code leads to us fixing all leftover window
273        # bugs at the source, we can kill the code below.
274
275        # Let's grab the location where we were called from to report if
276        # we have to force-kill the existing window (which normally
277        # should not happen).
278        frameline = None
279        try:
280            frame = inspect.currentframe()
281            if frame is not None:
282                frame = frame.f_back
283            if frame is not None:
284                frameinfo = inspect.getframeinfo(frame)
285                frameline = f'{frameinfo.filename} {frameinfo.lineno}'
286        except Exception:
287            logging.exception('Error calcing line for set_main_window')
288
289        # NOTE: disabling this for now since hopefully our new system
290        # will be bulletproof enough to avoid this. Can turn it back on
291        # if that's not the case.
292
293        # With our legacy main-menu system, the caller is responsible
294        # for clearing out the old main menu window when assigning the
295        # new. However there are corner cases where that doesn't happen
296        # and we get old windows stuck under the new main one. So let's
297        # guard against that. However, we can't simply delete the
298        # existing main window when a new one is assigned because the
299        # user may transition the old out *after* the assignment. Sigh.
300        # So, as a happy medium, let's check in on the old after a short
301        # bit of time and kill it if its still alive. That will be a bit
302        # ugly on screen but at least should un-break things.
303        def _delay_kill() -> None:
304            import time
305
306            if existing:
307                print(
308                    f'Killing old main_menu_window'
309                    f' when called at: {frameline} t={time.time():.3f}'
310                )
311                existing.delete()
312
313        if bool(False):
314            babase.apptimer(1.0, _delay_kill)
315
316        if is_back:
317            pass
318        else:
319            # When navigating forward, generate a back-window-state from
320            # the outgoing window.
321
322            # Exception is when we were passed a group and it matches
323            # the existing group; in that case we just keep the existing
324            # back-state.
325            if group_id is not None and group_id == self.main_window_group_id:
326                assert not is_top_level
327                print(f'GOT GROUP ID MATCH {group_id}; KEEPING BACK STATE.')
328                oldwin = self._main_window()
329                if oldwin is None:
330                    # We currenty only hold weak refs to windows so
331                    # that they are free to die on their own, but we
332                    # expect the main menu window to keep itself
333                    # alive as long as its the main one. Holler if
334                    # that seems to not be happening.
335                    logging.warning(
336                        'set_main_window: no existing MainWindow found'
337                        ' (and is_top_level is False); should not happen.'
338                        ' a MainWindow should keep itself alive as long'
339                        ' as it is main.'
340                    )
341                    window.main_window_back_state = None
342                else:
343                    window.main_window_back_state = (
344                        oldwin.main_window_back_state
345                    )
346            else:
347                if is_top_level:
348                    # Top level windows don't have or expect anywhere to go
349                    # back to.
350                    # self._main_window_back_state = None
351                    window.main_window_back_state = None
352                elif back_state is not None:
353                    window.main_window_back_state = back_state
354                else:
355                    oldwin = self._main_window()
356                    if oldwin is None:
357                        # We currenty only hold weak refs to windows so
358                        # that they are free to die on their own, but we
359                        # expect the main menu window to keep itself
360                        # alive as long as its the main one. Holler if
361                        # that seems to not be happening.
362                        logging.warning(
363                            'set_main_window: No old MainWindow found'
364                            ' and is_top_level is False;'
365                            ' this should not happen.'
366                        )
367                        window.main_window_back_state = None
368                    else:
369                        oldwinstate = oldwin.get_main_window_state()
370
371                        # Store our previous back state on this new one.
372                        oldwinstate.parent = oldwin.main_window_back_state
373                        window.main_window_back_state = oldwinstate
374
375        self._main_window = window_weakref
376        self._main_window_widget = window_widget
377        self.main_window_group_id = group_id
378
379    def has_main_window(self) -> bool:
380        """Return whether a main menu window is present."""
381        return bool(self._main_window_widget)
382
383    def clear_main_window(self) -> None:
384        """Clear any existing main window."""
385        from bauiv1._uitypes import MainWindow
386
387        main_window = self._main_window()
388        if main_window:
389            main_window.main_window_close()
390        else:
391            # Fallback; if we have a widget but no window, nuke the widget.
392            if self._main_window_widget:
393                logging.error(
394                    'Have _main_window_widget but no main_window'
395                    ' on clear_main_window; unexpected.'
396                )
397                self._main_window_widget.delete()
398
399        self._main_window = empty_weakref(MainWindow)
400        self._main_window_widget = None
401        self.main_window_group_id = None

Consolidated UI functionality for the app.

Category: App Classes

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

main_window_group_id: str | None
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
110    @property
111    def available(self) -> bool:
112        """Can uiv1 currently be used?
113
114        Code that may run in headless mode, before the UI has been spun up,
115        while other ui systems are active, etc. can check this to avoid
116        likely erroring.
117        """
118        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: