Source code for bacommon.docui.routes.docuitest

# Released under the MIT License. See LICENSE for details.
#
"""Routes for the doc-ui test domain."""

from __future__ import annotations  # Docs-generation hack.

from enum import Enum
from dataclasses import dataclass
from typing import TYPE_CHECKING, Annotated, override

from efro.dataclassio import ioprepped, IOAttrs

import bacommon.docui.v2 as dui2
from bacommon.docui.routes import (
    DocUIRoute,
    DocUILocalActionBase,
    DocUIState,
    PopupRoute,
    family_members,
)

if TYPE_CHECKING:
    import bacommon.docui.v2


[docs] class TestRoute(DocUIRoute): """Family class for doc-ui test routes."""
[docs] @override @classmethod def get_route_types(cls) -> tuple[type[DocUIRoute], ...]: return family_members(AnyTestRoute)
[docs] @override @classmethod def get_window_layout(cls) -> dui2.WindowLayout: # Test pages are dense showcases built for the full-size # window. return dui2.WindowLayout.LARGE
[docs] class ThemeVariant(Enum): """A look test pages can show their content in. The page-themes test pages show each; the root page's 'Layout Test Options' pick one for its 'Layout Tests' pages. """ DEFAULT = 'default' POPUP_LOOK = 'popup_look' BUTTON_COLOR = 'button_color' ROLE_COLORS = 'role_colors' TIGHT = 'tight' ROOMY = 'roomy' DARK = 'dark' TEXT_STYLES = 'text_styles'
[docs] @ioprepped @dataclass class Root(TestRoute, path='/'): """The root test page.""" #: Draw bounds and other debug bits. debug: bool = False #: Have the response include some client-effects. test_effects: bool = False #: Have the response include a local-action. test_action: bool = False
[docs] @ioprepped @dataclass class RootState(DocUIState, state_id='docuitest.root'): """State for the root test page.""" #: The window layout picked in its 'Layout Test Options' (for rows #: below to open their pages at), and at viewer layouts which side #: the viewer pane sits on (None for no pane). layout: dui2.WindowLayout = dui2.WindowLayout.WIDE_TALL viewer_side: dui2.ViewerSide | None = None #: The theme picked in its 'Layout Test Options' (for rows below to #: show their pages in). theme: ThemeVariant = ThemeVariant.DEFAULT
[docs] @ioprepped @dataclass class Test2(TestRoute, path='/test2'): """A second simple page."""
[docs] @ioprepped @dataclass class Slow(TestRoute, path='/slow'): """A page that takes a while to load."""
[docs] @ioprepped @dataclass class TimedActions(TestRoute, path='/timedactions'): """A page that counts down via timed-actions and then closes.""" val: int = 5
[docs] @ioprepped @dataclass class DisplayItems(TestRoute, path='/displayitems'): """Display-item tests.""" debug: bool = False
[docs] @ioprepped @dataclass class TextImages(TestRoute, path='/textimages'): """Text-with-images tests.""" debug: bool = False
[docs] @ioprepped @dataclass class LiveTimes(TestRoute, path='/livetimes'): """Time-varying text (countdowns, elapsed times) that ticks itself."""
[docs] @ioprepped @dataclass class TextWrapping(TestRoute, path='/textwrapping'): """Titles, subtitles and footnotes word-wrapped to their columns.""" debug: bool = False
[docs] @ioprepped @dataclass class Animation(TestRoute, path='/animation'): """Decorations animated by keyframe client-effects."""
[docs] @ioprepped @dataclass class Depictions(TestRoute, path='/depictions'): """Depiction tests.""" debug: bool = False
[docs] @ioprepped @dataclass class Viewers(TestRoute, path='/viewers'): """Live viewer tests: good cases and bad data, one shown at a time. The page's own controls switch cases, and the pane's style and side (page state), in place. """ #: Which case the viewer pane shows (a test-page case name; unknown #: names show the first). case: str = ''
[docs] @override @classmethod def get_window_layout(cls) -> dui2.WindowLayout: return dui2.WindowLayout.VIEWER
[docs] @ioprepped @dataclass class Names(TestRoute, path='/names'): """Name depiction tests (basic and capsule forms).""" debug: bool = False
[docs] @ioprepped @dataclass class NinePatch(TestRoute, path='/ninepatch'): """9-patch image tests (stretched and tile-fit, tinted).""" debug: bool = False
[docs] @ioprepped @dataclass class Glyphs(TestRoute, path='/glyphs'): """Custom glyphs against their measured bounds and anchor points."""
[docs] @ioprepped @dataclass class GlyphsState(DocUIState, state_id='docuitest.glyphs'): """What the glyphs test page draws over its glyphs.""" crosshairs: bool = True bounds: bool = True
[docs] @ioprepped @dataclass class EmptyPage(TestRoute, path='/emptypage'): """A page with nothing on it."""
[docs] @ioprepped @dataclass class BoundsTests(TestRoute, path='/boundstests'): """Button-style bounds tests."""
[docs] @ioprepped @dataclass class Widgets(TestRoute, path='/widgets'): """Control rows and page state.""" debug: bool = False #: See :attr:`WindowLayouts.viewer_side`. viewer_side: dui2.ViewerSide | None = None #: See :attr:`WindowLayouts.theme`. theme: ThemeVariant = ThemeVariant.DEFAULT
[docs] class ControlRowKind(Enum): """Kinds of control row (for :class:`NavTest`).""" CHECKBOX = 'checkbox' TEXT_INPUT = 'text' CHOICE = 'choice' SLIDER = 'slider' NUMBER = 'number' COLOR = 'color' BUTTON = 'button'
[docs] @ioprepped @dataclass class Drill(TestRoute, path='/drill'): """The top of a drill-down; shows what was found at the bottom. Exploring opens levels in windows of their own, several deep; picking something at the bottom closes all of them at once and hands the pick back here (see :meth:`~bacommon.docui.routes.DocUIRoute.close_to`). """
[docs] @ioprepped @dataclass class DrillLevel(TestRoute, path='/drill/level'): """One level of the drill-down, each in a window of its own.""" #: How deep this is (1 is the first level below the top). depth: int = 1 #: Doors taken to get here, in order. trail: str = ''
[docs] @ioprepped @dataclass class DrillState(DocUIState, state_id='docuitest.drill'): """State for the drill-down's top page.""" #: What was picked at the bottom, handed back up (empty for #: nothing yet). found: str = ''
[docs] @ioprepped @dataclass class PopupsState(DocUIState, state_id='docuitest.popups'): """State for the popups test page, which its popups act on.""" #: A color picked in a popup (empty for none yet). color: str = '' #: Bumped by popups replacing the page. count: int = 0 #: Set by the button opening a popup (handed along to it). opened_with: str = '' #: Scroll edge style for the popups this page opens. edge_style: dui2.ScrollEdgeStyle = dui2.ScrollEdgeStyle.FADE
[docs] @ioprepped @dataclass class Popups(TestRoute, path='/popups'): """Popup windows: opening them and what they can do to this page."""
[docs] @ioprepped @dataclass class PopupsMain(TestRoute, PopupRoute[PopupsState], path='/popups/main'): """The main test popup: acts on its parent every way there is."""
[docs] @override @classmethod def get_popup_size(cls) -> tuple[float, float]: # Lots to show. return (600.0, 400.0)
[docs] @ioprepped @dataclass class PopupsOther(TestRoute, PopupRoute[PopupsState], path='/popups/other'): """A second popup, swapped in for the first (and back)."""
[docs] @ioprepped @dataclass class PopupsMainState(DocUIState, state_id='docuitest.popupsmain'): """The main test popup's own state (apart from its parent's).""" #: Bumped by the popup replacing its own page. bumps: int = 0
[docs] @ioprepped @dataclass class ThemesState(DocUIState, state_id='docuitest.themes'): """State for the page-themes test pages (and their popups).""" #: Backs the sample checkbox. checked: bool = False
[docs] @ioprepped @dataclass class Themes(TestRoute, path='/themes'): """Page themes: the same content shown with various look values.""" variant: ThemeVariant = ThemeVariant.DEFAULT
[docs] @override @classmethod def get_window_layout(cls) -> dui2.WindowLayout: # A column, so insets and spacing are easy to compare. return dui2.WindowLayout.WIDE_TALL
[docs] @ioprepped @dataclass class ThemesPopup(TestRoute, PopupRoute[ThemesState], path='/themes/popup'): """The page-themes sample content in a popup.""" variant: ThemeVariant = ThemeVariant.DEFAULT
[docs] @override @classmethod def get_popup_size(cls) -> tuple[float, float]: return (600.0, 400.0)
[docs] @ioprepped @dataclass class Sections(TestRoute, path='/sections'): """Sections: headings, notes, backings and spacing between them.""" debug: bool = False #: See :attr:`WindowLayouts.viewer_side`. viewer_side: dui2.ViewerSide | None = None #: See :attr:`WindowLayouts.theme`. theme: ThemeVariant = ThemeVariant.DEFAULT
[docs] @override @classmethod def get_window_layout(cls) -> dui2.WindowLayout: # A column, like the settings pages sections mostly live in. return dui2.WindowLayout.SMALL_TALLER
[docs] @ioprepped @dataclass class ButtonGrids(TestRoute, path='/buttongrids'): """Grid button rows: columns, cells, alignment and navigation. Browse to it at several layouts; grids reflow to fit each width. """ debug: bool = False #: See :attr:`WindowLayouts.viewer_side`. viewer_side: dui2.ViewerSide | None = None #: See :attr:`WindowLayouts.theme`. theme: ThemeVariant = ThemeVariant.DEFAULT
[docs] @ioprepped @dataclass class WindowLayouts(TestRoute, path='/windowlayouts'): """Assorted rows for judging window layouts (browse to it at each). Control rows, titles and button rows at every alignment, and long rows, which in a narrow column scroll within it. """ debug: bool = False #: The layout we're being browsed to at, for the page to name (the #: browse sets the actual layout; this is just for show). layout: dui2.WindowLayout | None = None #: Which side the page's viewer pane (a turning punch powerup box) #: sits on at viewer layouts, or None for a page with no viewer #: (its rows then span the window). Ignored at other layouts. viewer_side: dui2.ViewerSide | None = None #: The theme to show the page in (its frame colors come with the #: link that opens it). theme: ThemeVariant = ThemeVariant.DEFAULT
[docs] @ioprepped @dataclass class LayoutFit(TestRoute, path='/layoutfit'): """A row exactly as big as a layout promises (browse at that layout). ``layout`` says which layout it's being shown in. The row fills the content rect the layout promises (see :data:`bacommon.docui.v2.SMALL_CONTENT_WIDTH` and friends), less the page's insets, and should never scroll; with ``over`` / ``wide_over`` set it's a hair taller / wider, which should scroll where the promise is exact (and may not where it's only a minimum). Viewer layouts show a blank viewer pane, as their promises are for a page beside one. """ over: bool = False wide_over: bool = False layout: dui2.WindowLayout = dui2.WindowLayout.WIDE_TALL #: See :attr:`WindowLayouts.theme`. (The page keeps its own #: vertical inset, which its row's size is figured from.) theme: ThemeVariant = ThemeVariant.DEFAULT
[docs] class Flavor(Enum): """Something to pick from on the widgets test page.""" VANILLA = 'v' CHOCOLATE = 'c' STRAWBERRY = 's' MINT = 'm'
[docs] class Size(Enum): """Something else to pick from on the widgets test page.""" S = 's' M = 'm' L = 'l' XL = 'x'
[docs] @ioprepped @dataclass class WidgetTestState(DocUIState, state_id='docuitest.widgets'): """State for the widgets test page.""" #: A checkbox with no on-change action. plain: bool = False #: A checkbox that re-requests the page when changed. live: bool = False #: Disabled checkboxes (selectable; not toggleable), one checked and #: one not. checked_disabled: bool = True unchecked_disabled: bool = False #: Nothing shows or edits this directly; it simply rides along #: (and gets bumped by a button). presses: int = 0 #: Text inputs with labels of assorted lengths. text_short: str = '' text_medium: str = 'Some text' text_long: str = '' #: A text input that re-requests the page when an edit is committed. text_live: str = '' #: A disabled text input (selectable; not editable). text_disabled: str = 'Not editable' #: Choices, plain and live. flavor: Flavor = Flavor.VANILLA flavor_live: Flavor = Flavor.VANILLA #: Short choice labels (a compact button) and an absurdly long one. size: Size = Size.M size_long: Size = Size.M #: An optional choice -- a 'nothing' option ahead of the flavors. topping: Flavor | None = None #: Choices over an arbitrary string set (the page defines the #: options, not an enum); plain and optional. difficulty: str = 'normal' region: str | None = None #: Disabled choice rows (selectable; menus won't open), one of each #: form: enum, optional enum, str, optional str. flavor_disabled: Flavor = ( Flavor.CHOCOLATE ) topping_disabled: Flavor | None = ( None ) difficulty_disabled: str = 'hard' region_disabled: str | None = 'eu' #: A slider value shaped like the sound-settings ones -- 0-1 by 0.05. volume: float = 0.5 #: A disabled slider (selectable; not adjustable). volume_disabled: float = 0.3 #: A number-row value shaped like a series length -- 1-21 by 2. series_length: float = 7.0 #: A disabled number row (selectable; not adjustable). series_length_disabled: float = 5.0 #: An rgb color, edited via the color-picker popup. tint: tuple[float, float, float] = ( 0.5, 0.25, 1.0, ) #: A disabled color row (selectable; picker won't open). tint_disabled: tuple[float, float, float] = (1.0, 0.6, 0.1) #: Checkboxes on rows demonstrating control-row spacing and header / #: footer bands (they drive nothing). spacing_top_demo: bool = False spacing_bottom_demo: bool = False bands_demo: bool = False
[docs] @ioprepped @dataclass class WebTestGet(TestRoute, path='/webtest/get'): """A page fetched from a web server via GET."""
[docs] @ioprepped @dataclass class WebTestPost( TestRoute, path='/webtest/post', method=dui2.RequestMethod.POST ): """A page fetched from a web server via POST."""
[docs] @ioprepped @dataclass class CloudMsgTestGet(TestRoute, path='/cloudmsgtest/get'): """A page fetched through our cloud connection via GET."""
[docs] @ioprepped @dataclass class CloudMsgTestPost( TestRoute, path='/cloudmsgtest/post', method=dui2.RequestMethod.POST ): """A page fetched through our cloud connection via POST."""
# All routes in the family. Handlers can match against this with # assert_never to be sure they cover everything. AnyTestRoute = ( Root | Test2 | Slow | TimedActions | DisplayItems | TextImages | LiveTimes | TextWrapping | Animation | Depictions | Viewers | Names | NinePatch | Glyphs | EmptyPage | BoundsTests | Widgets | NavTest | Drill | DrillLevel | Popups | PopupsMain | PopupsOther | Themes | ThemesPopup | Sections | ButtonGrids | WindowLayouts | LayoutFit | WebTestGet | WebTestPost | CloudMsgTestGet | CloudMsgTestPost )
[docs] class TestLocalAction(DocUILocalActionBase): """Family class for doc-ui test local-actions."""
[docs] @override @classmethod def get_action_types(cls) -> tuple[type[DocUILocalActionBase], ...]: return family_members(AnyTestLocalAction)
[docs] @ioprepped @dataclass class TestAction(TestLocalAction, name='testaction'): """Show a message proving we got here.""" testparam: int
# All local-actions in the family (just the one for now).
[docs] @ioprepped @dataclass class ShowVolume(TestLocalAction, name='showvolume'): """Show the volume slider's live value (fired while dragging)."""
AnyTestLocalAction = TestAction | ShowVolume # 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, TypeIs) import asyncio from concurrent.futures import Future from pathlib import Path from enum import Enum