# Released under the MIT License. See LICENSE for details.
#
"""Examples/tests for using DocUI to build UIs."""
from __future__ import annotations # Docs-generation hack.
from typing import TYPE_CHECKING, override, assert_never
from bacommon.langstr import LangStrSpecValue
import bacommon.docui.routes.docuitest as rt
import bauiv1 as bui
from bauiv1 import _commonassets
from bauiv1 import _uiv1assets, _classiccatalogassets
from bauiv1 import _classiccharacterassets
from bauiv1 import _classicassets
from bauiv1lib.docui import DocUIWindow, TypedDocUIController
if TYPE_CHECKING:
from bacommon.docui import DocUIResponse
import bacommon.docui.v2
import bacommon.docui.routes.docuitest
from bauiv1lib.docui import DocUILocalAction
[docs]
def show_test_doc_ui_v2_window() -> None:
"""Bust out a doc-ui test window built locally on the client.
Test pages authored as language-agnostic v2 documents — text as
``LangStrSpec`` values from the ``badocuiv2testassets`` package (decoded
in the client's locale at render time), textures/meshes as typed
refs, and multi-line labels via wrap-params instead of hand-baked
newlines. The Cloud-Msg and Web buttons fetch equivalent v2 pages
from bamaster, keeping the full cloud/web resolve -> decode ->
render paths exercised.
"""
bui.app.ui_v1.auxiliary_window_activate(
win_type=DocUIWindow,
win_create_call=bui.CallStrict(
TestDocUIV2Controller().create_window, rt.Root()
),
win_extra_type_id=TestDocUIV2Controller.get_window_extra_type_id(),
)
[docs]
class TestDocUIV2Controller(
TypedDocUIController[rt.AnyTestRoute, rt.AnyTestLocalAction]
):
"""Tests/demonstrations of native (v2 / l-string) docui.
Local pages are authored client-side; the web-test and
cloud-msg-test routes fetch equivalent v2 pages from bamaster.
"""
[docs]
@override
@classmethod
def get_route_type(cls) -> type[bacommon.docui.routes.docuitest.TestRoute]:
return rt.TestRoute
[docs]
@override
@classmethod
def get_local_action_type(
cls,
) -> type[bacommon.docui.routes.docuitest.TestLocalAction]:
return rt.TestLocalAction
[docs]
@override
def fulfill_route(
self, route: bacommon.docui.routes.docuitest.AnyTestRoute
) -> DocUIResponse:
"""Fulfill a route (called in a background thread)."""
import bacommon.docui.v2 as dui2
response = self._fulfill_route_page(route)
# No horizontal scroll bars anywhere on our test pages (page
# arrows and drag/wheel scrolling still carry their rows).
if isinstance(response, dui2.Response):
for row in dui2.all_rows(response.page.rows):
if isinstance(row, dui2.ButtonRow):
row.show_scrollbar = False
return response
def _fulfill_route_page(
self, route: bacommon.docui.routes.docuitest.AnyTestRoute
) -> DocUIResponse:
# A flat route-to-page dispatch; every case adds a branch and a
# return.
# pylint: disable=too-many-return-statements, too-many-branches
match route:
# Handle some pages purely locally.
case rt.Root():
return _test_v2_page_root(route)
case rt.Test2():
from bauiv1lib.docuitestsimple import test_page_2
return test_page_2()
case rt.Slow():
from bauiv1lib.docuitestsimple import test_page_long
return test_page_long()
case rt.TimedActions():
from bauiv1lib.docuitestsimple import (
test_page_timed_actions,
)
return test_page_timed_actions(route)
case rt.DisplayItems():
from bauiv1lib.docuitestitems import test_page_display_items
return test_page_display_items(route)
case rt.TextImages():
from bauiv1lib.docuitesttextimages import (
test_page_text_images,
)
return test_page_text_images(route)
case rt.LiveTimes():
from bauiv1lib.docuitestlivetimes import test_page_live_times
return test_page_live_times(route)
case rt.TextWrapping():
from bauiv1lib.docuitesttextwrap import (
test_page_text_wrapping,
)
return test_page_text_wrapping(route)
case rt.Animation():
from bauiv1lib.docuitestanimation import test_page_animation
return test_page_animation()
case rt.Depictions():
from bauiv1lib.docuitestdepictions import (
test_page_depictions,
)
return test_page_depictions(route)
case rt.Viewers():
from bauiv1lib.docuitestviewers import test_page_viewers
return test_page_viewers(route)
case rt.Names():
from bauiv1lib.docuitestnames import test_page_names
return test_page_names(route)
case rt.NinePatch():
from bauiv1lib.docuitestninepatch import (
test_page_nine_patch,
)
return test_page_nine_patch(route)
case rt.EmptyPage():
from bauiv1lib.docuitestsimple import test_page_empty
return test_page_empty()
case rt.BoundsTests():
from bauiv1lib.docuitestlayouts import test_page_bounds
return test_page_bounds()
case rt.Widgets():
from bauiv1lib.docuitestwidgets import test_page_widgets
return test_page_widgets(route)
case rt.NavTest():
from bauiv1lib.docuitestnav import test_page_nav
return test_page_nav(route)
case rt.WindowLayouts():
from bauiv1lib.docuitestlayouts import (
test_page_window_layouts,
)
return test_page_window_layouts(route)
case rt.Sections():
from bauiv1lib.docuitestsections import test_page_sections
return test_page_sections(route)
case rt.ButtonGrids():
from bauiv1lib.docuitestgrids import test_page_button_grids
return test_page_button_grids(route)
case rt.WideFit():
from bauiv1lib.docuitestlayouts import test_page_wide_fit
return test_page_wide_fit(route)
# Ship web-tests off to some webserver to handle.
case rt.WebTestGet() | rt.WebTestPost():
return self.fulfill_request_web(
route, 'https://www.ballistica.net/docuitest'
)
# Ship cloud-msg-tests through our cloud connection.
case rt.CloudMsgTestGet() | rt.CloudMsgTestPost():
return self.fulfill_request_cloud(route, 'docuitestv2')
case _:
assert_never(route)
[docs]
@override
def run_local_action(
self,
action: bacommon.docui.routes.docuitest.AnyTestLocalAction,
context: DocUILocalAction,
) -> None:
match action:
case rt.ShowVolume():
# The live value: the row wrote it to page state before
# firing us.
state = context.state(rt.WidgetTestState)
vol = (
'unknown'
if state is None
else f'{round(state.volume * 100)}%'
)
bui.screenmessage(
f'Volume: {vol} (trigger={context.trigger!r})'
)
case rt.TestAction():
bui.screenmessage(f'Would do {action!r}.')
case _:
assert_never(action)
def _layout_title(layout: bacommon.docui.v2.WindowLayout) -> str:
"""A window layout's name, title-cased (``WIDE_TALL`` -> ``Wide Tall``)."""
return layout.name.replace('_', ' ').title()
def _side_name(side: bacommon.docui.v2.ViewerSide | None) -> str:
"""A viewer pane placement's name (None for no pane)."""
return 'None' if side is None else side.name.title()
def _window_layouts_row(
style: bacommon.docui.v2.ScrollEdgeStyle,
) -> bacommon.docui.v2.ButtonRow:
"""A row opening each window layout (and fit test) in a style.
Viewer layouts get a button for each viewer pane placement: left,
none (rows spanning the window) and right.
"""
import bacommon.docui.v2 as dui2
from bauiv1lib.docui._panes import layout_has_viewer_pane
def _buttons(layout: dui2.WindowLayout) -> list[dui2.Button]:
name = layout.name.replace('_', ' ').title()
if not layout_has_viewer_pane(layout):
return [
dui2.Button(
label=LangStrSpecValue.literal(name),
size=(120, 80),
action=rt.WindowLayouts(layout=layout).browse(
layout=layout, scroll_edge_style=style
),
)
]
sides = (dui2.ViewerSide.LEFT, None, dui2.ViewerSide.RIGHT)
return [
dui2.Button(
label=LangStrSpecValue.literal(f'{name} ({_side_name(side)})'),
size=(200, 80),
action=rt.WindowLayouts(layout=layout, viewer_side=side).browse(
layout=layout, scroll_edge_style=style
),
)
for side in sides
]
return dui2.ButtonRow(
# Dev-only, so baked literals.
title=LangStrSpecValue.literal(
f'Window Layouts ({style.name.lower()} scroll)'
),
buttons=[
*(b for layout in dui2.WindowLayout for b in _buttons(layout)),
*(
dui2.Button(
label=LangStrSpecValue.literal(
_layout_title(layout) + ' Fit'
),
size=(120, 80),
action=rt.WideFit(layout=layout).browse(
layout=layout, scroll_edge_style=style
),
)
for layout in (
dui2.WindowLayout.WIDE,
dui2.WindowLayout.WIDE_TALL,
dui2.WindowLayout.WIDE_TALLER,
dui2.WindowLayout.WIDER,
)
),
],
)
def _test_v2_page_root(
route: bacommon.docui.routes.docuitest.Root,
) -> bacommon.docui.v2.Response:
"""Author the v2 (l-string) test root page purely on the client.
The full v1 test root page, with all text authored as
language-agnostic ``LangStrSpec`` values from the ``badocuiv2testassets``
package, textures/meshes as typed refs from
``_commonassets``/``_classicassets``, and multi-line labels wrapped via
definition-time :class:`~bacommon.langstr.WrapParams` on the
package's string definitions (decision D-t) instead of v1's
hand-baked newlines. The client resolves the referenced packages
in its own locale, decodes, and wraps -- so this single response
renders in any language.
"""
import bacommon.clienteffect as clfx
import bacommon.docui.v2 as dui2
from bauiv1 import _docuiv2testassets
from bauiv1lib.docuitestlayouts import layout_test_decos
strs = _docuiv2testassets.strings
# Show some specific debug bits if they ask us to.
debug = route.debug
# The long-row test's buttons all reload this page unchanged, to
# check that scroll positions survive a reload.
reload = route.replace()
def _long_row_button(num: int) -> bacommon.docui.v2.Button:
# Dev-only page, so baked literals.
return dui2.Button(
label=LangStrSpecValue.literal(str(num)),
size=(150, 100),
action=reload,
)
response = dui2.Response(
page=dui2.Page(
title=strs.nav.test_root_title.spec,
rows=[
dui2.ButtonRow(
debug=debug,
header_height=100,
header_decorations_left=[
dui2.Text(
text=strs.common.header_left.spec,
position=(0, 10 + 20),
color=(1, 1, 1, 0.3),
size=(150, 30),
h_align=dui2.HAlign.LEFT,
debug=debug,
),
],
header_decorations_center=[
dui2.Text(
text=strs.common.hello_from_docui.spec,
position=(0, 10 + 20),
size=(300, 30),
debug=debug,
),
dui2.Text(
text=strs.common.docui_reference.spec,
scale=0.5,
position=(0, -18 + 20),
size=(600, 23),
debug=debug,
),
dui2.Image(
texture=_classicassets.textures.nub,
position=(0, -58 + 20),
size=(60, 60),
),
],
header_decorations_right=[
dui2.Text(
text=strs.common.header_right.spec,
position=(0, 10 + 20),
color=(1, 1, 1, 0.3),
size=(150, 30),
h_align=dui2.HAlign.RIGHT,
debug=debug,
),
],
title=strs.nav.some_tests.spec,
buttons=[
dui2.Button(
label=strs.nav.browse.spec,
size=(120, 80),
action=rt.Test2().browse(),
),
dui2.Button(
label=strs.nav.replace.spec,
size=(120, 80),
action=rt.Test2().replace(),
),
dui2.Button(
label=strs.nav.close.spec,
size=(120, 80),
action=dui2.Local(close_window=True),
),
dui2.Button(
label=strs.common.invalid_request.spec,
size=(120, 80),
# A path with no route, to exercise error
# handling; routes can't express that (by
# design) so we build it by hand.
# pylint: disable-next=docui-raw-request
action=dui2.Browse(dui2.Request('/invalidrequest')),
),
dui2.Button(
label=strs.effects.immediate_client_effects.spec,
size=(120, 80),
action=dui2.Local(
# V2 effect forms: l-string text decoded
# in the client's locale + a typed sound
# ref from an asset package. The page
# resolve pre-warms the referenced
# packages, so press-time runs are
# cache hits.
immediate_client_effects=[
clfx.ScreenMessageV2(
message=(
strs.effects.immediate_effects_hello
).spec,
color=(0, 1, 0),
),
clfx.PlaySoundV2(
sound=(
_commonassets.audio
).cash_register,
),
clfx.Delay(1.0),
clfx.ScreenMessageV2(
message=(
strs.effects.effect_success
).spec,
color=(0, 1, 0),
),
clfx.PlaySoundV2(
sound=(
_commonassets.audio
).cash_register,
),
]
),
),
dui2.Button(
label=strs.effects.response_client_effects.spec,
size=(120, 80),
action=rt.Root(test_effects=True).browse(),
),
dui2.Button(
label=strs.effects.immediate_local_action.spec,
size=(120, 80),
action=rt.TestAction(testparam=123).local(),
),
dui2.Button(
label=strs.effects.response_local_action.spec,
size=(120, 80),
action=rt.Root(test_action=True).browse(),
),
],
),
dui2.ButtonRow(
title=strs.nav.few_more_tests.spec,
buttons=[
dui2.Button(
label=(
strs.common.hide_debug.spec
if debug
else strs.common.show_debug.spec
),
size=(120, 80),
action=rt.Root(debug=not debug).replace(),
),
dui2.Button(
label=strs.nav.slow_browse.spec,
size=(120, 80),
action=rt.Slow().browse(),
),
dui2.Button(
label=strs.nav.slow_replace.spec,
size=(120, 80),
action=rt.Slow().replace(),
),
dui2.Button(
label=strs.common.timed_actions.spec,
size=(120, 80),
action=rt.TimedActions().browse(),
),
dui2.Button(
label=strs.web.web_get.spec,
size=(120, 80),
action=rt.WebTestGet().browse(),
),
dui2.Button(
label=strs.web.web_post.spec,
size=(120, 80),
action=rt.WebTestPost().browse(),
),
dui2.Button(
label=strs.items.display_items.spec,
size=(120, 80),
action=rt.DisplayItems().browse(),
),
dui2.Button(
# Dev-only page, so a baked literal label.
label=LangStrSpecValue.literal('Text Images'),
size=(120, 80),
action=rt.TextImages().browse(),
),
dui2.Button(
# Dev-only page, so a baked literal label.
label=LangStrSpecValue.literal('Live Times'),
size=(120, 80),
action=rt.LiveTimes().browse(),
),
dui2.Button(
# Dev-only page, so a baked literal label.
label=LangStrSpecValue.literal('Text Wrapping'),
size=(120, 80),
action=rt.TextWrapping().browse(),
),
dui2.Button(
# Dev-only page, so a baked literal label.
label=LangStrSpecValue.literal('Animation'),
size=(120, 80),
action=rt.Animation().browse(),
),
dui2.Button(
# Dev-only page, so a baked literal label.
label=LangStrSpecValue.literal('Depictions'),
size=(120, 80),
action=rt.Depictions().browse(),
),
dui2.Button(
# Dev-only page, so a baked literal label.
label=LangStrSpecValue.literal('Viewers'),
size=(120, 80),
action=rt.Viewers().browse(),
),
dui2.Button(
# Dev-only page, so a baked literal label.
label=LangStrSpecValue.literal('Viewers (Narrow)'),
size=(160, 80),
action=rt.Viewers().browse(
layout=dui2.WindowLayout.VIEWER_NARROW
),
),
dui2.Button(
# Dev-only page, so a baked literal label.
label=LangStrSpecValue.literal('Names'),
size=(120, 80),
action=rt.Names().browse(),
),
dui2.Button(
# Dev-only page, so a baked literal label.
label=LangStrSpecValue.literal('9-Patch'),
size=(120, 80),
action=rt.NinePatch().browse(),
),
dui2.Button(
label=strs.layout.empty_page.spec,
size=(120, 80),
action=rt.EmptyPage().browse(),
),
],
),
dui2.ButtonRow(
title=strs.nav.even_more_tests.spec,
buttons=[
dui2.Button(
label=strs.cloud.cloud_msg_get.spec,
size=(120, 80),
action=rt.CloudMsgTestGet().browse(),
),
dui2.Button(
label=strs.cloud.cloud_msg_post.spec,
size=(120, 80),
action=rt.CloudMsgTestPost().browse(),
),
dui2.Button(
label=strs.layout.bounds_tests.spec,
size=(120, 80),
action=rt.BoundsTests().browse(),
),
# Dev-only page, so baked literal labels.
dui2.Button(
label=LangStrSpecValue.literal('Widgets Small'),
size=(120, 80),
action=rt.Widgets().browse(
layout=dui2.WindowLayout.SMALL
),
),
dui2.Button(
label=LangStrSpecValue.literal(
'Widgets Small Tall'
),
size=(120, 80),
action=rt.Widgets().browse(
layout=dui2.WindowLayout.SMALL_TALL
),
),
dui2.Button(
label=LangStrSpecValue.literal(
'Widgets Small Taller'
),
size=(120, 80),
action=rt.Widgets().browse(
layout=dui2.WindowLayout.SMALL_TALLER
),
),
dui2.Button(
label=LangStrSpecValue.literal('Widgets Wide'),
size=(120, 80),
action=rt.Widgets().browse(
layout=dui2.WindowLayout.WIDE
),
),
dui2.Button(
label=LangStrSpecValue.literal('Widgets Wider'),
size=(120, 80),
action=rt.Widgets().browse(
layout=dui2.WindowLayout.WIDER
),
),
dui2.Button(
label=LangStrSpecValue.literal('Widgets Large'),
size=(120, 80),
action=rt.Widgets().browse(
layout=dui2.WindowLayout.LARGE
),
),
dui2.Button(
label=LangStrSpecValue.literal('Nav Test'),
size=(120, 80),
action=rt.NavTest().browse(
layout=dui2.WindowLayout.SMALL
),
),
],
),
# One row per scroll edge style, so the two can be
# compared directly (content should sit in exactly the
# same place in both).
*(
_window_layouts_row(style)
for style in (
dui2.ScrollEdgeStyle.FADE,
dui2.ScrollEdgeStyle.OUTLINE,
)
),
dui2.ButtonRow(
# Dev-only, so baked literals. (Every layout, to see
# how sections and cards behave at each width.)
title=LangStrSpecValue.literal('Sections'),
buttons=[
dui2.Button(
label=LangStrSpecValue.literal(
layout.name.replace('_', ' ').title()
),
size=(120, 80),
action=rt.Sections().browse(layout=layout),
)
for layout in dui2.WindowLayout
],
),
dui2.ButtonRow(
# Dev-only, so baked literals. (Every layout, since
# grids reflow to each width.)
title=LangStrSpecValue.literal('Button Grids'),
buttons=[
dui2.Button(
label=LangStrSpecValue.literal(
layout.name.replace('_', ' ').title()
),
size=(120, 80),
action=rt.ButtonGrids().browse(layout=layout),
)
for layout in dui2.WindowLayout
],
),
dui2.ButtonRow(title=strs.layout.empty_row.spec, buttons=[]),
dui2.ButtonRow(
title=strs.layout.layout_tests.spec,
debug=debug,
buttons=[
dui2.Button(
label=strs.nav.test.spec,
size=(180, 200),
decorations=layout_test_decos(debug),
),
dui2.Button(
label=strs.nav.test_two.spec,
size=(100, 100),
color=(1, 0, 0, 1),
label_color=(1, 1, 1, 1),
padding_right=4,
),
# Should look like the first button but
# scaled down.
dui2.Button(
label=strs.nav.test.spec,
size=(180, 200),
scale=0.6,
padding_bottom=30, # Should nudge us up.
debug=debug, # Show bounds.
decorations=layout_test_decos(debug),
),
# Testing custom button images and opacity.
dui2.Button(
label=strs.nav.test_three.spec,
texture=_uiv1assets.textures.button_square_wide,
padding_left=10.0,
padding_right=10.0,
color=(1, 1, 1, 0.3),
size=(200, 100),
style=dui2.ButtonStyle.MEDIUM,
),
# Testing image drawing vs bounds
dui2.Button(
label=strs.layout.bounds_test.spec,
texture=_commonassets.textures.white,
color=(1, 1, 1, 0.3),
size=(150, 100),
debug=debug,
),
],
),
dui2.ButtonRow(
title=strs.layout.long_row_test.spec,
# Dev-only page, so baked literals.
subtitle=LangStrSpecValue.literal(
'Look - a subtitle! - These buttons reload the page'
' - all scrolling should remain intact.'
),
footnote=LangStrSpecValue.literal('Look - a footnote!'),
buttons=[
dui2.Button(
size=(150, 100),
action=reload,
decorations=[
dui2.Text(
text=strs.layout.max_width_test.spec,
position=(0, 25),
size=(150 * 0.8, 32.0),
flatness=1.0,
shadow=0.0,
debug=debug,
),
# v1 bakes a newline into this one
# ('MaxHeightTest\nSecondLine'); we ask
# for two balanced lines instead.
dui2.Text(
text=strs.layout.max_height_test.spec,
position=(0, -20),
size=(150 * 0.8, 40),
flatness=1.0,
shadow=0.0,
debug=debug,
),
],
),
dui2.Button(
size=(150, 100),
action=reload,
decorations=[
dui2.Image(
texture=(
_classiccharacterassets.textures
).zoe_icon,
position=(0, 0),
size=(70, 70),
tint_texture=(
_classiccharacterassets.textures
).zoe_icon_color_mask,
tint_color=(1, 0, 0),
tint2_color=(0, 1, 0),
mask_texture=(
_classiccatalogassets.textures
).character_icon_mask,
),
],
),
dui2.Button(
size=(150, 100),
action=reload,
decorations=[
dui2.Image(
texture=(
_classiccatalogassets.textures
).bridgit_preview,
position=(0, 10),
size=(120, 60),
mask_texture=(
(
_classiccatalogassets.textures
).map_preview_mask
),
mesh_opaque=(
_classiccatalogassets.meshes
).level_select_button_opaque,
mesh_transparent=(
_classiccatalogassets.meshes
).level_select_button_transparent,
),
],
),
*[_long_row_button(i) for i in range(1, 9)],
dui2.Button(
label=strs.common.foo.spec,
size=(150, 100),
scale=0.4,
padding_left=100,
padding_right=200,
action=reload,
),
*[_long_row_button(i) for i in range(9, 17)],
],
),
dui2.ButtonRow(
subtitle=strs.layout.subtitle_only.spec,
buttons=[
dui2.Button(size=(200, 120)),
],
),
# Same again, so a subtitle-only row can be judged
# against a plain button row above it as well as
# against a footnote.
dui2.ButtonRow(
# Dev-only page, so a baked literal.
subtitle=LangStrSpecValue.literal('Subtitle only 2'),
buttons=[
dui2.Button(size=(200, 120)),
],
),
dui2.ButtonRow(
buttons=[
dui2.Button(
label=strs.layout.row_with_no_title.spec,
size=(300, 80),
style=dui2.ButtonStyle.MEDIUM,
color=(0.8, 0.8, 0.8, 1),
icon=_classicassets.textures.button_punch,
icon_color=(0.5, 0.3, 1.0, 1.0),
icon_scale=1.2,
),
],
),
dui2.ButtonRow(
title=strs.layout.centered_faded_title.spec,
title_color=(0.6, 0.6, 1.0, 0.3),
title_flatness=1.0,
title_shadow=1.0,
subtitle=strs.layout.testing_centered.spec,
subtitle_color=(1.0, 0.5, 1.0, 0.5),
subtitle_flatness=1.0,
subtitle_shadow=0.0,
# Styled on its own, not inheriting the subtitle's
# overrides. (Dev-only page, so a baked literal.)
footnote=LangStrSpecValue.literal(
'Footnote with its own color, flatness and shadow.'
),
footnote_color=(0.4, 1.0, 1.0, 1.0),
footnote_flatness=0.0,
footnote_shadow=1.0,
center_content=True,
center_title=True,
buttons=[
dui2.Button(
label=strs.common.hello_there.spec,
size=(200, 120),
color=(0.7, 0.7, 0.9, 1),
),
],
),
# Right-alignment tests. (Dev-only, so baked literals.)
dui2.ButtonRow(
title=LangStrSpecValue.literal('Right Aligned Title'),
subtitle=LangStrSpecValue.literal(
'Testing right-aligned title, subtitle, and content'
),
title_align=dui2.HAlign.RIGHT,
content_align=dui2.HAlign.RIGHT,
debug=debug,
buttons=[
dui2.Button(
label=LangStrSpecValue.literal('Right'),
size=(200, 80),
style=dui2.ButtonStyle.MEDIUM,
debug=debug,
),
],
),
# Same, but with more buttons than fit; alignment only
# applies to rows with room to spare, so this one should
# simply fill its width and scroll like any other.
dui2.ButtonRow(
title=LangStrSpecValue.literal('Right Aligned Scrolling'),
subtitle=LangStrSpecValue.literal(
'Too many buttons to fit; should scroll normally'
),
title_align=dui2.HAlign.RIGHT,
content_align=dui2.HAlign.RIGHT,
debug=debug,
buttons=[
dui2.Button(
label=LangStrSpecValue.literal(str(i + 1)),
size=(150, 100),
)
for i in range(16)
],
# The first row's header test, flipped upside down
# as a footer.
footer_height=100,
footer_decorations_left=[
dui2.Text(
text=LangStrSpecValue.literal('Footer Left'),
position=(0, -10 - 20),
color=(1, 1, 1, 0.3),
size=(150, 30),
h_align=dui2.HAlign.LEFT,
debug=debug,
),
],
footer_decorations_center=[
dui2.Image(
texture=_classicassets.textures.nub,
position=(0, 58 - 20),
size=(60, 60),
),
dui2.Text(
text=strs.common.docui_reference.spec,
scale=0.5,
position=(0, 18 - 20),
size=(600, 23),
debug=debug,
),
dui2.Text(
text=LangStrSpecValue.literal(
'Hello from a DocUI footer!'
),
position=(0, -10 - 20),
size=(300, 30),
debug=debug,
),
],
footer_decorations_right=[
dui2.Text(
text=LangStrSpecValue.literal('Footer Right'),
position=(0, -10 - 20),
color=(1, 1, 1, 0.3),
size=(150, 30),
h_align=dui2.HAlign.RIGHT,
debug=debug,
),
],
),
],
)
)
# Include some client effects if they ask (the 'Response
# ClientEffects' button). V2 effect forms; see the immediate-effects
# note above.
if route.test_effects:
response.client_effects = [
clfx.ScreenMessageV2(
message=strs.effects.response_effects_hello.spec,
color=(0, 1, 0),
),
clfx.PlaySoundV2(sound=_commonassets.audio.cash_register),
clfx.Delay(1.0),
clfx.ScreenMessageV2(
message=strs.effects.effect_success.spec, color=(0, 1, 0)
),
clfx.PlaySoundV2(sound=_commonassets.audio.cash_register),
]
# Include a local-action if they ask (the 'Response LocalAction'
# button).
if route.test_action:
rt.TestAction(testparam=234).attach(response)
return response
# 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