bauiv1

Ballistica user interface api version 1

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

A high level directive given to the app.

Category: App Classes

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

Tells the app to simply run in its default mode.

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

Tells the app to exec some Python code.

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

A high level mode for the app.

Category: App Classes

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

Return the overall experience provided by this mode.

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

Return whether this mode can handle the provided intent.

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

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

Handle an intent.

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

Called when the mode is being activated.

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

Called when the mode is being deactivated.

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

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

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

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

Return the current app-time in seconds.

Category: General Utility Functions

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

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

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

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

Category: General Utility Functions

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

Arguments
time (float)

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

call (Callable[[], Any])

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

Examples

Print some stuff through time:

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

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

Category: General Utility Classes

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

Arguments
time

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

call

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

repeat

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

Example

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

AppTimer(time: float, call: Callable[[], Any], repeat: bool = False)
95    def __init__(
96        self, time: float, call: Callable[[], Any], repeat: bool = False
97    ) -> None:
98        pass
class BasicMainWindowState(bauiv1.MainWindowState):
263class BasicMainWindowState(MainWindowState):
264    """A basic MainWindowState holding a lambda to recreate a MainWindow."""
265
266    def __init__(
267        self,
268        create_call: Callable[
269            [
270                Literal['in_right', 'in_left', 'in_scale'] | None,
271                bauiv1.Widget | None,
272            ],
273            bauiv1.MainWindow,
274        ],
275    ) -> None:
276        super().__init__()
277        self.create_call = create_call
278
279    @override
280    def create_window(
281        self,
282        transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
283        origin_widget: bauiv1.Widget | None = None,
284    ) -> bauiv1.MainWindow:
285        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])
266    def __init__(
267        self,
268        create_call: Callable[
269            [
270                Literal['in_right', 'in_left', 'in_scale'] | None,
271                bauiv1.Widget | None,
272            ],
273            bauiv1.MainWindow,
274        ],
275    ) -> None:
276        super().__init__()
277        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:
279    @override
280    def create_window(
281        self,
282        transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
283        origin_widget: bauiv1.Widget | None = None,
284    ) -> bauiv1.MainWindow:
285        return self.create_call(transition, origin_widget)

Create a window based on this state.

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

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

Create or edit a button widget.

Category: User Interface Functions

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

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

Get a unicode string representing a special character.

Category: General Utility Functions

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

See babase.SpecialChar for the list of available characters.

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

Create or edit a check-box widget.

Category: User Interface Functions

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

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

Return whether this platform supports clipboard operations at all.

Category: General Utility Functions

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

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

Copy a string to the system clipboard.

Category: General Utility Functions

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

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

Create or edit a column widget.

Category: User Interface Functions

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

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

Create or edit a container widget.

Category: User Interface Functions

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

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

Store or use a ballistica context.

Category: General Utility Classes

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

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

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

Usage

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

Example

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

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

Return a ContextRef pointing to no context.

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

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

Whether the context was created as empty.

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

Whether the context has expired.

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

Return the current display-time in seconds.

Category: General Utility Functions

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

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

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

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

Category: General Utility Functions

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

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

Arguments
time (float)

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

call (Callable[[], Any])

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

Examples

Print some stuff through time:

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

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

Category: General Utility Classes

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

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

Arguments
time

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

call

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

repeat

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

Example

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

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

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

Category: General Utility Functions

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

Example

This print will only fire for the first loop iteration:

>>> for i in range(10):
... if babase.do_once():
...     print('HelloWorld once from loop!')
def get_input_idle_time() -> float:
1003def get_input_idle_time() -> float:
1004    """Return seconds since any local input occurred (touch, keypress, etc.)."""
1005    return float()

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

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

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

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

Return a QR code texture.

The provided url must be 64 bytes or less.

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

Return a full type name including module for a class.

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

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

Category: General Utility Functions

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

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

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

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

Load a sound for use in the ui.

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

Load a texture for use in the ui.

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

Create or edit a horizontal scroll widget.

Category: User Interface Functions

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

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

Create or edit an image widget.

Category: User Interface Functions

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

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

name: str

Displays when user selecting this keyboard.

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

Used for row/column lengths.

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

Extra chars like emojis.

nums: tuple[str, ...]

The 'num' page.

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

Allows using implicit login types in an explicit way.

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

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

Should be called for each adapter in on_app_loading.

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

Keep the adapter informed of implicit login states.

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

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

Keep the adapter informed of actively used logins.

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

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

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

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

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

Attempt to sign in via this adapter.

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

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

Is this adapter's back-end currently active?

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

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

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

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

Describes the final result of a sign-in attempt.

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

Describes the current state of an implicit login.

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

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

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

Used to define strings in a language-independent way.

Category: General Utility Classes

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

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

Examples

EXAMPLE 1: specify a string from a resource path

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

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

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

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

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

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

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

Instantiate a Lstr.

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

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

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

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

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

Return whether the Lstr is a 'flat' value.

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

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

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

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

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

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

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

Create a MainWindow given a root widget and transition info.

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

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

Get window transitioning out if still alive.

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

Is this MainWindow allowed to change the global main window?

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

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

Move back in the main window stack.

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

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

Replace ourself with a new MainWindow.

def on_main_window_close(self) -> None:
224    def on_main_window_close(self) -> None:
225        """Called before transitioning out a main window.
226
227        A good opportunity to save window state/etc.
228        """

Called before transitioning out a main window.

A good opportunity to save window state/etc.

def get_main_window_state(self) -> MainWindowState:
230    def get_main_window_state(self) -> MainWindowState:
231        """Return a WindowState to recreate this window, if supported."""
232        raise NotImplementedError()

Return a WindowState to recreate this window, if supported.

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

Persistent state for a specific MainWindow.

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

parent: MainWindowState | None
is_top_level: bool | None
is_auxiliary: bool | None
window_type: type[MainWindow] | None
selection: str | None
def create_window( self, transition: Optional[Literal['in_right', 'in_left', 'in_scale']] = None, origin_widget: _bauiv1.Widget | None = None) -> MainWindow:
250    def create_window(
251        self,
252        transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
253        origin_widget: bauiv1.Widget | None = None,
254    ) -> MainWindow:
255        """Create a window based on this state.
256
257        WindowState child classes should override this to recreate their
258        particular type of window.
259        """
260        raise NotImplementedError()

Create a window based on this state.

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

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

Category: User Interface Classes

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

Exception raised when a referenced object does not exist.

Category: Exception Classes

def open_url(address: str, force_fallback: bool = False) -> None:
1321def open_url(address: str, force_fallback: bool = False) -> None:
1322    """Open the provided URL.
1323
1324    Category: **General Utility Functions**
1325
1326    Attempts to open the provided url in a web-browser. If that is not
1327    possible (or force_fallback is True), instead displays the url as
1328    a string and/or qrcode.
1329    """
1330    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:
1333def overlay_web_browser_close() -> bool:
1334    """Close any open overlay web browser.
1335
1336    Category: **General Utility Functions**
1337    """
1338    return bool()

Close any open overlay web browser.

Category: General Utility Functions

def overlay_web_browser_is_open() -> bool:
1341def overlay_web_browser_is_open() -> bool:
1342    """Return whether an overlay web browser is open currently.
1343
1344    Category: **General Utility Functions**
1345    """
1346    return bool()

Return whether an overlay web browser is open currently.

Category: General Utility Functions

def overlay_web_browser_is_supported() -> bool:
1349def overlay_web_browser_is_supported() -> bool:
1350    """Return whether an overlay web browser is supported here.
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 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:
1361def overlay_web_browser_open_url(address: str) -> None:
1362    """Open the provided URL in an overlayw web browser.
1363
1364    Category: **General Utility Functions**
1365
1366    An overlay web browser is a small dialog that pops up over the top
1367    of the main engine window. It can be used for performing simple
1368    tasks such as sign-ins.
1369    """
1370    return None

Open the provided URL in an overlayw web browser.

Category: General Utility Functions

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

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

Permissions that can be requested from the OS.

Category: Enums

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

A plugin to alter app behavior in some way.

Category: App Classes

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

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

Called when the app reaches the running state.

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

Called when the app enters the suspended state.

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

Called when the app exits the suspended state.

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

Called when the app is beginning the shutdown process.

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

Called when the app has completed the shutdown process.

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

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

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

Called to show our settings UI.

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

Represents a plugin the engine knows about.

Category: App Classes

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

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

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

Whether the user wants this plugin to load.

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

Possibly load the plugin and log any errors.

def pushcall( call: Callable, from_other_thread: bool = False, suppress_other_thread_warning: bool = False, other_thread_use_fg_context: bool = False, raw: bool = False) -> None:
1405def pushcall(
1406    call: Callable,
1407    from_other_thread: bool = False,
1408    suppress_other_thread_warning: bool = False,
1409    other_thread_use_fg_context: bool = False,
1410    raw: bool = False,
1411) -> None:
1412    """Push a call to the logic event-loop.
1413    Category: **General Utility Functions**
1414
1415    This call expects to be used in the logic thread, and will automatically
1416    save and restore the babase.Context to behave seamlessly.
1417
1418    If you want to push a call from outside of the logic thread,
1419    however, you can pass 'from_other_thread' as True. In this case
1420    the call will always run in the UI context_ref on the logic thread
1421    or whichever context_ref is in the foreground if
1422    other_thread_use_fg_context is True.
1423    Passing raw=True will disable thread checks and context_ref sets/restores.
1424    """
1425    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:
1429def quit(
1430    confirm: bool = False, quit_type: babase.QuitType | None = None
1431) -> None:
1432    """Quit the app.
1433
1434    Category: **General Utility Functions**
1435
1436    If 'confirm' is True, a confirm dialog will be presented if conditions
1437    allow; otherwise the quit will still be immediate.
1438    See docs for babase.QuitType for explanations of the optional
1439    'quit_type' arg.
1440    """
1441    return None

Quit the app.

Category: General Utility Functions

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

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

Types of input a controller can send to the game.

Category: Enums

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

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

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

SOFT = <QuitType.SOFT: 0>
BACK = <QuitType.BACK: 1>
HARD = <QuitType.HARD: 2>
def rowwidget( edit: _bauiv1.Widget | None = None, parent: _bauiv1.Widget | None = None, size: Optional[Sequence[float]] = None, position: Optional[Sequence[float]] = None, background: bool | None = None, selected_child: _bauiv1.Widget | None = None, visible_child: _bauiv1.Widget | None = None, claims_left_right: bool | None = None, selection_loops_to_parent: bool | None = None) -> _bauiv1.Widget:
468def rowwidget(
469    edit: bauiv1.Widget | None = None,
470    parent: bauiv1.Widget | None = None,
471    size: Sequence[float] | None = None,
472    position: Sequence[float] | None = None,
473    background: bool | None = None,
474    selected_child: bauiv1.Widget | None = None,
475    visible_child: bauiv1.Widget | None = None,
476    claims_left_right: bool | None = None,
477    selection_loops_to_parent: bool | None = None,
478) -> bauiv1.Widget:
479    """Create or edit a row widget.
480
481    Category: **User Interface Functions**
482
483    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
484    a new one is created and returned. Arguments that are not set to None
485    are applied to the Widget.
486    """
487    import bauiv1  # pylint: disable=cyclic-import
488
489    return bauiv1.Widget()

Create or edit a row widget.

Category: User Interface Functions

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

def safecolor( color: Sequence[float], target_intensity: float = 0.6) -> tuple[float, ...]:
1479def safecolor(
1480    color: Sequence[float], target_intensity: float = 0.6
1481) -> tuple[float, ...]:
1482    """Given a color tuple, return a color safe to display as text.
1483
1484    Category: **General Utility Functions**
1485
1486    Accepts tuples of length 3 or 4. This will slightly brighten very
1487    dark colors, etc.
1488    """
1489    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:
1492def screenmessage(
1493    message: str | babase.Lstr,
1494    color: Sequence[float] | None = None,
1495    log: bool = False,
1496) -> None:
1497    """Print a message to the local client's screen, in a given color.
1498
1499    Category: **General Utility Functions**
1500
1501    Note that this version of the function is purely for local display.
1502    To broadcast screen messages in network play, look for methods such as
1503    broadcastmessage() provided by the scene-version packages.
1504    """
1505    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, autoselect: bool | None = None) -> _bauiv1.Widget:
492def scrollwidget(
493    *,
494    edit: bauiv1.Widget | None = None,
495    parent: bauiv1.Widget | None = None,
496    size: Sequence[float] | None = None,
497    position: Sequence[float] | None = None,
498    background: bool | None = None,
499    selected_child: bauiv1.Widget | None = None,
500    capture_arrows: bool = False,
501    on_select_call: Callable | None = None,
502    center_small_content: bool | None = None,
503    color: Sequence[float] | None = None,
504    highlight: bool | None = None,
505    border_opacity: float | None = None,
506    simple_culling_v: float | None = None,
507    selection_loops_to_parent: bool | None = None,
508    claims_left_right: bool | None = None,
509    claims_up_down: bool | None = None,
510    autoselect: bool | None = None,
511) -> bauiv1.Widget:
512    """Create or edit a scroll widget.
513
514    Category: **User Interface Functions**
515
516    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
517    a new one is created and returned. Arguments that are not set to None
518    are applied to the Widget.
519    """
520    import bauiv1  # pylint: disable=cyclic-import
521
522    return bauiv1.Widget()

Create or edit a scroll widget.

Category: User Interface Functions

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

def set_analytics_screen(screen: str) -> None:
1508def set_analytics_screen(screen: str) -> None:
1509    """Used for analytics to see where in the app players spend their time.
1510
1511    Category: **General Utility Functions**
1512
1513    Generally called when opening a new window or entering some UI.
1514    'screen' should be a string description of an app location
1515    ('Main Menu', etc.)
1516    """
1517    return None

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

Category: General Utility Functions

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

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

Category: User Interface Classes

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

Play the sound locally.

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

Stop the sound if it is playing.

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

Special characters the game can print.

Category: Enums

DOWN_ARROW = <SpecialChar.DOWN_ARROW: 0>
UP_ARROW = <SpecialChar.UP_ARROW: 1>
LEFT_ARROW = <SpecialChar.LEFT_ARROW: 2>
RIGHT_ARROW = <SpecialChar.RIGHT_ARROW: 3>
TOP_BUTTON = <SpecialChar.TOP_BUTTON: 4>
LEFT_BUTTON = <SpecialChar.LEFT_BUTTON: 5>
RIGHT_BUTTON = <SpecialChar.RIGHT_BUTTON: 6>
BOTTOM_BUTTON = <SpecialChar.BOTTOM_BUTTON: 7>
DELETE = <SpecialChar.DELETE: 8>
SHIFT = <SpecialChar.SHIFT: 9>
BACK = <SpecialChar.BACK: 10>
LOGO_FLAT = <SpecialChar.LOGO_FLAT: 11>
REWIND_BUTTON = <SpecialChar.REWIND_BUTTON: 12>
PLAY_PAUSE_BUTTON = <SpecialChar.PLAY_PAUSE_BUTTON: 13>
FAST_FORWARD_BUTTON = <SpecialChar.FAST_FORWARD_BUTTON: 14>
DPAD_CENTER_BUTTON = <SpecialChar.DPAD_CENTER_BUTTON: 15>
PLAY_STATION_CROSS_BUTTON = <SpecialChar.PLAY_STATION_CROSS_BUTTON: 16>
PLAY_STATION_CIRCLE_BUTTON = <SpecialChar.PLAY_STATION_CIRCLE_BUTTON: 17>
PLAY_STATION_TRIANGLE_BUTTON = <SpecialChar.PLAY_STATION_TRIANGLE_BUTTON: 18>
PLAY_STATION_SQUARE_BUTTON = <SpecialChar.PLAY_STATION_SQUARE_BUTTON: 19>
PLAY_BUTTON = <SpecialChar.PLAY_BUTTON: 20>
PAUSE_BUTTON = <SpecialChar.PAUSE_BUTTON: 21>
OUYA_BUTTON_O = <SpecialChar.OUYA_BUTTON_O: 22>
OUYA_BUTTON_U = <SpecialChar.OUYA_BUTTON_U: 23>
OUYA_BUTTON_Y = <SpecialChar.OUYA_BUTTON_Y: 24>
OUYA_BUTTON_A = <SpecialChar.OUYA_BUTTON_A: 25>
TOKEN = <SpecialChar.TOKEN: 26>
TICKET = <SpecialChar.TICKET: 28>
DICE_BUTTON1 = <SpecialChar.DICE_BUTTON1: 31>
DICE_BUTTON2 = <SpecialChar.DICE_BUTTON2: 32>
DICE_BUTTON3 = <SpecialChar.DICE_BUTTON3: 33>
DICE_BUTTON4 = <SpecialChar.DICE_BUTTON4: 34>
PARTY_ICON = <SpecialChar.PARTY_ICON: 36>
TEST_ACCOUNT = <SpecialChar.TEST_ACCOUNT: 37>
TICKET_BACKING = <SpecialChar.TICKET_BACKING: 38>
TROPHY1 = <SpecialChar.TROPHY1: 39>
TROPHY2 = <SpecialChar.TROPHY2: 40>
TROPHY3 = <SpecialChar.TROPHY3: 41>
TROPHY0A = <SpecialChar.TROPHY0A: 42>
TROPHY0B = <SpecialChar.TROPHY0B: 43>
TROPHY4 = <SpecialChar.TROPHY4: 44>
LOCAL_ACCOUNT = <SpecialChar.LOCAL_ACCOUNT: 45>
FLAG_UNITED_STATES = <SpecialChar.FLAG_UNITED_STATES: 47>
FLAG_MEXICO = <SpecialChar.FLAG_MEXICO: 48>
FLAG_GERMANY = <SpecialChar.FLAG_GERMANY: 49>
FLAG_BRAZIL = <SpecialChar.FLAG_BRAZIL: 50>
FLAG_RUSSIA = <SpecialChar.FLAG_RUSSIA: 51>
FLAG_CHINA = <SpecialChar.FLAG_CHINA: 52>
FLAG_UNITED_KINGDOM = <SpecialChar.FLAG_UNITED_KINGDOM: 53>
FLAG_CANADA = <SpecialChar.FLAG_CANADA: 54>
FLAG_INDIA = <SpecialChar.FLAG_INDIA: 55>
FLAG_JAPAN = <SpecialChar.FLAG_JAPAN: 56>
FLAG_FRANCE = <SpecialChar.FLAG_FRANCE: 57>
FLAG_INDONESIA = <SpecialChar.FLAG_INDONESIA: 58>
FLAG_ITALY = <SpecialChar.FLAG_ITALY: 59>
FLAG_SOUTH_KOREA = <SpecialChar.FLAG_SOUTH_KOREA: 60>
FLAG_NETHERLANDS = <SpecialChar.FLAG_NETHERLANDS: 61>
FEDORA = <SpecialChar.FEDORA: 62>
HAL = <SpecialChar.HAL: 63>
CROWN = <SpecialChar.CROWN: 64>
YIN_YANG = <SpecialChar.YIN_YANG: 65>
EYE_BALL = <SpecialChar.EYE_BALL: 66>
SKULL = <SpecialChar.SKULL: 67>
HEART = <SpecialChar.HEART: 68>
DRAGON = <SpecialChar.DRAGON: 69>
HELMET = <SpecialChar.HELMET: 70>
MUSHROOM = <SpecialChar.MUSHROOM: 71>
NINJA_STAR = <SpecialChar.NINJA_STAR: 72>
VIKING_HELMET = <SpecialChar.VIKING_HELMET: 73>
MOON = <SpecialChar.MOON: 74>
SPIDER = <SpecialChar.SPIDER: 75>
FIREBALL = <SpecialChar.FIREBALL: 76>
FLAG_UNITED_ARAB_EMIRATES = <SpecialChar.FLAG_UNITED_ARAB_EMIRATES: 77>
FLAG_QATAR = <SpecialChar.FLAG_QATAR: 78>
FLAG_EGYPT = <SpecialChar.FLAG_EGYPT: 79>
FLAG_KUWAIT = <SpecialChar.FLAG_KUWAIT: 80>
FLAG_ALGERIA = <SpecialChar.FLAG_ALGERIA: 81>
FLAG_SAUDI_ARABIA = <SpecialChar.FLAG_SAUDI_ARABIA: 82>
FLAG_MALAYSIA = <SpecialChar.FLAG_MALAYSIA: 83>
FLAG_CZECH_REPUBLIC = <SpecialChar.FLAG_CZECH_REPUBLIC: 84>
FLAG_AUSTRALIA = <SpecialChar.FLAG_AUSTRALIA: 85>
FLAG_SINGAPORE = <SpecialChar.FLAG_SINGAPORE: 86>
FLAG_IRAN = <SpecialChar.FLAG_IRAN: 90>
FLAG_POLAND = <SpecialChar.FLAG_POLAND: 91>
FLAG_ARGENTINA = <SpecialChar.FLAG_ARGENTINA: 92>
FLAG_PHILIPPINES = <SpecialChar.FLAG_PHILIPPINES: 93>
FLAG_CHILE = <SpecialChar.FLAG_CHILE: 94>
MIKIROG = <SpecialChar.MIKIROG: 95>
def supports_unicode_display() -> bool:
1657def supports_unicode_display() -> bool:
1658    """Return whether we can display all unicode characters in the gui."""
1659    return bool()

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

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

Category: User Interface Classes

def textwidget( *, edit: _bauiv1.Widget | None = None, parent: _bauiv1.Widget | None = None, size: Optional[Sequence[float]] = None, position: Optional[Sequence[float]] = None, text: str | Lstr | None = None, v_align: str | None = None, h_align: str | None = None, editable: bool | None = None, padding: float | None = None, on_return_press_call: Optional[Callable[[], NoneType]] = None, on_activate_call: Optional[Callable[[], NoneType]] = None, selectable: bool | None = None, query: _bauiv1.Widget | None = None, max_chars: int | None = None, color: Optional[Sequence[float]] = None, click_activate: bool | None = None, on_select_call: Optional[Callable[[], NoneType]] = None, always_highlight: bool | None = None, draw_controller: _bauiv1.Widget | None = None, scale: float | None = None, corner_scale: float | None = None, description: str | Lstr | None = None, transition_delay: float | None = None, maxwidth: float | None = None, max_height: float | None = None, flatness: float | None = None, shadow: float | None = None, autoselect: bool | None = None, rotate: float | None = None, enabled: bool | None = None, force_internal_editing: bool | None = None, always_show_carat: bool | None = None, big: bool | None = None, extra_touch_border_scale: float | None = None, res_scale: float | None = None, query_max_chars: _bauiv1.Widget | None = None, query_description: _bauiv1.Widget | None = None, adapter_finished: bool | None = None, glow_type: str | None = None, allow_clear_button: bool | None = None) -> _bauiv1.Widget:
530def textwidget(
531    *,
532    edit: bauiv1.Widget | None = None,
533    parent: bauiv1.Widget | None = None,
534    size: Sequence[float] | None = None,
535    position: Sequence[float] | None = None,
536    text: str | bauiv1.Lstr | None = None,
537    v_align: str | None = None,
538    h_align: str | None = None,
539    editable: bool | None = None,
540    padding: float | None = None,
541    on_return_press_call: Callable[[], None] | None = None,
542    on_activate_call: Callable[[], None] | None = None,
543    selectable: bool | None = None,
544    query: bauiv1.Widget | None = None,
545    max_chars: int | None = None,
546    color: Sequence[float] | None = None,
547    click_activate: bool | None = None,
548    on_select_call: Callable[[], None] | None = None,
549    always_highlight: bool | None = None,
550    draw_controller: bauiv1.Widget | None = None,
551    scale: float | None = None,
552    corner_scale: float | None = None,
553    description: str | bauiv1.Lstr | None = None,
554    transition_delay: float | None = None,
555    maxwidth: float | None = None,
556    max_height: float | None = None,
557    flatness: float | None = None,
558    shadow: float | None = None,
559    autoselect: bool | None = None,
560    rotate: float | None = None,
561    enabled: bool | None = None,
562    force_internal_editing: bool | None = None,
563    always_show_carat: bool | None = None,
564    big: bool | None = None,
565    extra_touch_border_scale: float | None = None,
566    res_scale: float | None = None,
567    query_max_chars: bauiv1.Widget | None = None,
568    query_description: bauiv1.Widget | None = None,
569    adapter_finished: bool | None = None,
570    glow_type: str | None = None,
571    allow_clear_button: bool | None = None,
572) -> bauiv1.Widget:
573    """Create or edit a text widget.
574
575    Category: **User Interface Functions**
576
577    Pass a valid existing bauiv1.Widget as 'edit' to modify it; otherwise
578    a new one is created and returned. Arguments that are not set to None
579    are applied to the Widget.
580    """
581    import bauiv1  # pylint: disable=cyclic-import
582
583    return bauiv1.Widget()

Create or edit a text widget.

Category: User Interface Functions

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

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

Generate a babase.Lstr for displaying a time value.

Category: General Utility Functions

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

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

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

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

Category: User Interface Functions

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

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

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

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

Category: Enums

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

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

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

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

Consolidated UI functionality for the app.

Category: App Classes

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

quit_window: _bauiv1.Widget | None
window_states: dict[type, typing.Any]
cleanupchecks: list[bauiv1._uitypes.UICleanupCheck]
upkeeptimer: _babase.AppTimer | None
title_color
heading_color
infotextcolor
root_ui_calls: dict[UIV1AppSubsystem.RootUIElement, typing.Callable[[], NoneType]]
available: bool
107    @property
108    def available(self) -> bool:
109        """Can uiv1 currently be used?
110
111        Code that may run in headless mode, before the UI has been spun up,
112        while other ui systems are active, etc. can check this to avoid
113        likely erroring.
114        """
115        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:
117    @override
118    def reset(self) -> None:
119        from bauiv1._uitypes import MainWindow
120
121        self.root_ui_calls.clear()
122        self._main_window = empty_weakref(MainWindow)
123        self._main_window_widget = None

Reset the subsystem to a default state.

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

uiscale: UIScale
125    @property
126    def uiscale(self) -> babase.UIScale:
127        """Current ui scale for the app."""
128        return self._uiscale

Current ui scale for the app.

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

Called when the app reaches the loading state.

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

def get_main_window(self) -> MainWindow | None:
166    def get_main_window(self) -> bauiv1.MainWindow | None:
167        """Return main window, if any."""
168        return self._main_window()

Return main window, if any.

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

Set the current 'main' window.

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

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

def has_main_window(self) -> bool:
336    def has_main_window(self) -> bool:
337        """Return whether a main menu window is present."""
338        return bool(self._main_window_widget)

Return whether a main menu window is present.

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

Clear any existing main window.

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

Fully initialize a window-state from a window.

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

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

Restore UI to a saved state.

@override
def on_screen_change(self) -> None:
397    @override
398    def on_screen_change(self) -> None:
399        # Update our stored UIScale.
400        self._update_ui_scale()
401
402        # Update native bits (allow root widget to rebuild itself/etc.)
403        _bauiv1.on_screen_change()
404
405        # Lastly, if we have a main window, recreate it to pick up the
406        # new UIScale/etc.
407        mainwindow = self.get_main_window()
408        if mainwindow is not None:
409            winstate = self.save_main_window_state(mainwindow)
410            self.clear_main_window(transition='instant')
411            self.restore_main_window_state(winstate)

Called when screen dimensions or ui-scale changes.

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

Stuff provided by the root ui.

MENU_BUTTON = <RootUIElement.MENU_BUTTON: 'menu_button'>
SQUAD_BUTTON = <RootUIElement.SQUAD_BUTTON: 'squad_button'>
ACCOUNT_BUTTON = <RootUIElement.ACCOUNT_BUTTON: 'account_button'>
SETTINGS_BUTTON = <RootUIElement.SETTINGS_BUTTON: 'settings_button'>
INBOX_BUTTON = <RootUIElement.INBOX_BUTTON: 'inbox_button'>
STORE_BUTTON = <RootUIElement.STORE_BUTTON: 'store_button'>
INVENTORY_BUTTON = <RootUIElement.INVENTORY_BUTTON: 'inventory_button'>
ACHIEVEMENTS_BUTTON = <RootUIElement.ACHIEVEMENTS_BUTTON: 'achievements_button'>
GET_TOKENS_BUTTON = <RootUIElement.GET_TOKENS_BUTTON: 'get_tokens_button'>
TICKETS_METER = <RootUIElement.TICKETS_METER: 'tickets_meter'>
TOKENS_METER = <RootUIElement.TOKENS_METER: 'tokens_meter'>
TROPHY_METER = <RootUIElement.TROPHY_METER: 'trophy_meter'>
LEVEL_METER = <RootUIElement.LEVEL_METER: 'level_meter'>
CHEST_SLOT_0 = <RootUIElement.CHEST_SLOT_0: 'chest_slot_0'>
CHEST_SLOT_1 = <RootUIElement.CHEST_SLOT_1: 'chest_slot_1'>
CHEST_SLOT_2 = <RootUIElement.CHEST_SLOT_2: 'chest_slot_2'>
CHEST_SLOT_3 = <RootUIElement.CHEST_SLOT_3: 'chest_slot_3'>
def utc_now_cloud() -> datetime.datetime:
29def utc_now_cloud() -> datetime.datetime:
30    """Returns estimated utc time regardless of local clock settings.
31
32    Applies offsets pulled from server communication/etc.
33    """
34    # FIXME - do something smart here.
35    return utc_now()

Returns estimated utc time regardless of local clock settings.

Applies offsets pulled from server communication/etc.

WeakCall = <class 'babase._general._WeakCall'>
def widget( *, edit: _bauiv1.Widget, up_widget: _bauiv1.Widget | None = None, down_widget: _bauiv1.Widget | None = None, left_widget: _bauiv1.Widget | None = None, right_widget: _bauiv1.Widget | None = None, show_buffer_top: float | None = None, show_buffer_bottom: float | None = None, show_buffer_left: float | None = None, show_buffer_right: float | None = None, depth_range: tuple[float, float] | None = None, autoselect: bool | None = None) -> None:
597def widget(
598    *,
599    edit: bauiv1.Widget,
600    up_widget: bauiv1.Widget | None = None,
601    down_widget: bauiv1.Widget | None = None,
602    left_widget: bauiv1.Widget | None = None,
603    right_widget: bauiv1.Widget | None = None,
604    show_buffer_top: float | None = None,
605    show_buffer_bottom: float | None = None,
606    show_buffer_left: float | None = None,
607    show_buffer_right: float | None = None,
608    depth_range: tuple[float, float] | None = None,
609    autoselect: bool | None = None,
610) -> None:
611    """Edit common attributes of any widget.
612
613    Category: **User Interface Functions**
614
615    Unlike other UI calls, this can only be used to edit, not to create.
616    """
617    return None

Edit common attributes of any widget.

Category: User Interface Functions

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

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

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

Category: User Interface Classes

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

transitioning_out: bool

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

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

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

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

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

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

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

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

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

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

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

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

Returns any child Widgets of this Widget.

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

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

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

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

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

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

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

A basic window.

Category: User Interface Classes

Essentially wraps a ContainerWidget with some higher level functionality.

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

Return the root widget.