Source code for bacommon.cloudui.v1

# Released under the MIT License. See LICENSE for details.
#
"""Full UIs defined in the cloud - similar to a basic form of html"""

from __future__ import annotations

from enum import Enum
from dataclasses import dataclass, field
from typing import Annotated, override, assert_never

from efro.dataclassio import ioprepped, IOAttrs, IOMultiType

from bacommon.cloudui._cloudui import CloudUIPage, CloudUIPageTypeID


[docs] class HAlign(Enum): """Horizontal alignment.""" LEFT = 'l' CENTER = 'c' RIGHT = 'r'
[docs] class VAlign(Enum): """Vertical alignment.""" TOP = 't' CENTER = 'c' BOTTOM = 'b'
[docs] class DecorationTypeID(Enum): """Type ID for each of our subclasses.""" UNKNOWN = 'u' TEXT = 't' IMAGE = 'i'
[docs] class Decoration(IOMultiType[DecorationTypeID]): """Top level class for our multitype."""
[docs] @override @classmethod def get_type_id(cls) -> DecorationTypeID: # Require child classes to supply this themselves. If we did a # full type registry/lookup here it would require us to import # everything and would prevent lazy loading. raise NotImplementedError()
[docs] @override @classmethod def get_type(cls, type_id: DecorationTypeID) -> type[Decoration]: # pylint: disable=cyclic-import t = DecorationTypeID if type_id is t.UNKNOWN: return UnknownDecoration if type_id is t.TEXT: return Text if type_id is t.IMAGE: return Image # Important to make sure we provide all types. assert_never(type_id)
[docs] @override @classmethod def get_unknown_type_fallback(cls) -> Decoration: # If we encounter some future type we don't know anything about, # drop in a placeholder. return UnknownDecoration()
[docs] @override @classmethod def get_type_id_storage_name(cls) -> str: return '_t'
[docs] @ioprepped @dataclass class UnknownDecoration(Decoration): """An unknown decoration. In practice these should never show up since the master-server generates these on the fly for the client and so should not send clients one they can't digest. """
[docs] @override @classmethod def get_type_id(cls) -> DecorationTypeID: return DecorationTypeID.UNKNOWN
[docs] @ioprepped @dataclass class Text(Decoration): """Text decoration.""" #: Note that cloud-ui accepts only raw :class:`str` values for text; #: use :meth:`babase.Lstr.evaluate()` or whatnot for multi-language #: support. text: str position: tuple[float, float] max_width: float max_height: float = 32.0 scale: float = 1.0 h_align: HAlign = ( HAlign.CENTER ) v_align: VAlign = ( VAlign.CENTER ) flatness: float | None = None shadow: float | None = None #: Show max-width/height bounds; useful during development. debug: bool = False
[docs] @override @classmethod def get_type_id(cls) -> DecorationTypeID: return DecorationTypeID.TEXT
[docs] @ioprepped @dataclass class Image(Decoration): """Image decoration.""" texture: str position: tuple[float, float] size: tuple[float, float] color: tuple[float, float, float] | None = None opacity: float | None = None h_align: HAlign = ( HAlign.CENTER ) v_align: VAlign = ( VAlign.CENTER ) tint_texture: str | None = ( None ) tint_color: tuple[float, float, float] | None = None tint2_color: tuple[float, float, float] | None = None mask_texture: str | None = ( None ) mesh_opaque: str | None = ( None ) mesh_transparent: str | None = None
[docs] @override @classmethod def get_type_id(cls) -> DecorationTypeID: return DecorationTypeID.IMAGE
[docs] @ioprepped @dataclass class Button: """A button in our cloud ui.""" #: Note that cloud-ui accepts only raw :class:`str` values for text; #: use :meth:`babase.Lstr.evaluate()` or whatnot for multi-language #: support. label: str | None = None size: tuple[float, float] | None = None color: tuple[float, float, float] | None = None opacity: float | None = None text_color: tuple[float, float, float, float] | None = None text_flatness: float | None = None text_scale: float | None = ( None ) texture: str | None = None scale: float = 1.0 padding_left: float = 0.0 padding_top: float = 0.0 padding_right: float = 0.0 padding_bottom: float = 0.0 decorations: list[Decoration] = field(default_factory=list) debug: bool = False
[docs] @ioprepped @dataclass class Row: """A row in our cloud ui.""" buttons: list[Button] #: Note that cloud-ui accepts only raw :class:`str` values for text; #: use :meth:`babase.Lstr.evaluate()` or whatnot for multi-language #: support. title: str | None = None title_color: tuple[float, float, float, float] | None = None title_flatness: float | None = None title_shadow: float | None = None subtitle: str | None = None subtitle_color: tuple[float, float, float, float] | None = None subtitle_flatness: float | None = None subtitle_shadow: float | None = None button_spacing: float = 5.0 padding_left: float = 10.0 padding_right: float = 10.0 padding_top: float = 10.0 padding_bottom: float = 10.0 center_content: bool = False center_title: bool = False #: If things disappear when scrolling left/right, turn this up. simple_culling_h: float = ( 100.0 ) debug: bool = False
[docs] @ioprepped @dataclass class Page(CloudUIPage): """Cloud-UI page version 1.""" #: Note that cloud-ui accepts only raw :class:`str` values for text; #: use :meth:`babase.Lstr.evaluate()` or whatnot for multi-language #: support. title: str rows: list[Row] #: If True, content smaller than the available height will be #: centered vertically. This can look natural for certain types of #: content such as confirmation dialogs. center_vertically: bool = ( False ) #: If things disappear when scrolling up and down, turn this up. simple_culling_v: float = ( 100.0 )
[docs] @override @classmethod def get_type_id(cls) -> CloudUIPageTypeID: return CloudUIPageTypeID.V1
# Docs-generation hack; import some stuff that we likely only forward-declared # in our actual source code so that docs tools can find it. from typing import (Coroutine, Any, Literal, Callable, Generator, Awaitable, Sequence, Self) import asyncio from concurrent.futures import Future from pathlib import Path from enum import Enum