Source code for bauiv1lib.docuitest

# Released under the MIT License. See LICENSE for details.
#
"""Examples/tests for using DocUI to build UIs."""

# pylint: disable=too-many-lines
from __future__ import annotations

import time
import copy
from typing import TYPE_CHECKING, override

from efro.error import CleanError
import bauiv1 as bui

from bauiv1lib.docui import DocUIWindow, DocUIController

if TYPE_CHECKING:
    from bacommon.docui import DocUIRequest, DocUIResponse
    import bacommon.docui.v1

    from bauiv1lib.docui import DocUILocalAction


[docs] def show_test_doc_ui_window() -> None: """Bust out a doc-ui window.""" import bacommon.docui.v1 as dui1 # Pop up an auxiliary window wherever we are in the nav stack. bui.app.ui_v1.auxiliary_window_activate( win_type=DocUIWindow, win_create_call=bui.CallStrict( TestDocUIController().create_window, dui1.Request('/') ), win_extra_type_id=TestDocUIController.get_window_extra_type_id(), )
[docs] class TestDocUIController(DocUIController): """Provides various tests/demonstrations of docui functionality."""
[docs] @override def fulfill_request(self, request: DocUIRequest) -> DocUIResponse: """Fulfill a request. Will be called in a background thread. """ # pylint: disable=too-many-return-statements import bacommon.docui.v1 as dui1 # We currently support v1 requests only. if not isinstance(request, dui1.Request): raise CleanError('Invalid request version.') # Handle some pages purely locally. if request.path == '/': return _test_page_root(request) if request.path == '/test2': return _test_page_2(request) if request.path == '/slow': return _test_page_long(request) if request.path == '/timedactions': return _test_page_timed_actions(request) if request.path == '/displayitems': return _test_page_display_items(request) if request.path == '/emptypage': return _test_page_empty(request) if request.path == '/boundstests': return _test_bounds(request) # Ship '/webtest/*' off to some webserver to handle. if request.path.startswith('/webtest/'): return self.fulfill_request_web( request, 'https://www.ballistica.net/docuitest' ) # Ship '/cloudmsgtest/*' through our cloud connection to handle. if request.path.startswith('/cloudmsgtest/'): return self.fulfill_request_cloud(request, 'test') raise CleanError('Invalid request path.')
[docs] @override def local_action(self, action: DocUILocalAction) -> None: bui.screenmessage( f'Would do {action.name!r} with args {action.args!r}.' )
def _test_page_long( request: bacommon.docui.v1.Request, ) -> bacommon.docui.v1.Response: """Testing a page that takes a bit of time to load.""" import bacommon.docui.v1 as dui1 del request # Unused. # Simulate a slow connection or whatnot. time.sleep(3.0) return dui1.Response( page=dui1.Page( title='Test', center_vertically=True, rows=[ dui1.ButtonRow( title='That took a while', center_title=True, center_content=True, buttons=[ dui1.Button( 'Sure Did', size=(120, 80), action=dui1.Browse(dui1.Request('/')), ), ], ), ], ) ) def _test_page_timed_actions( request: bacommon.docui.v1.Request, ) -> bacommon.docui.v1.Response: """Testing a page that takes a bit of time to load.""" import bacommon.docui.v1 as dui1 val = request.args.get('val') if not isinstance(val, int): val = 5 return dui1.Response( page=dui1.Page( title='Test', center_vertically=True, rows=[ dui1.ButtonRow( title=f'Hello there {val}', subtitle='Each change here is a new request/response.', center_title=True, center_content=True, buttons=[ dui1.Button( 'Done', size=(120, 80), action=dui1.Local(close_window=True), default=True, ), ], ), ], ), # Refresh this page with a countdown until we hit zero and then # close the window. timed_action=( dui1.Replace(dui1.Request('/timedactions', args={'val': val - 1})) if (val - 1) > 0 else dui1.Local(close_window=True) ), timed_action_delay=1.0, ) def _test_page_effects() -> bacommon.docui.v1.Page: """Testing effects after a page load.""" import bacommon.docui.v1 as dui1 return dui1.Page( title='Effects', center_vertically=True, rows=[ dui1.ButtonRow( title='Have some lovely effects', center_title=True, center_content=True, buttons=[ dui1.Button( 'Nice!', size=(120, 80), action=dui1.Local(close_window=True), ), ], ), ], ) def _test_page_2( request: bacommon.docui.v1.Request, ) -> bacommon.docui.v1.Response: """More testing.""" import bacommon.docui.v1 as dui1 del request # Unused. return dui1.Response( page=dui1.Page( title='Test 2', rows=[ dui1.ButtonRow( title='More Tests', buttons=[ dui1.Button( 'Browse', size=(120, 80), action=dui1.Browse(dui1.Request('/')), ), dui1.Button( 'Replace', size=(120, 80), action=dui1.Replace(dui1.Request('/')), ), dui1.Button( 'Close', size=(120, 80), action=dui1.Local(close_window=True), selected=True, # Testing this ), ], ), ], ) ) def _test_page_root( request: bacommon.docui.v1.Request, ) -> bacommon.docui.v1.Response: """Return test page.""" import bacommon.clienteffect as clfx import bacommon.docui.v1 as dui1 # Show some specific debug bits if they ask us to. debug = bool(request.args.get('debug', False)) response = dui1.Response( page=dui1.Page( title='Test Root', rows=[ dui1.ButtonRow( debug=debug, header_height=100, header_decorations_left=[ dui1.Text( 'HeaderLeft', position=(0, 10 + 20), color=(1, 1, 1, 0.3), size=(150, 30), h_align=dui1.HAlign.LEFT, debug=debug, ), ], header_decorations_center=[ dui1.Text( 'Hello From DocUI!', position=(0, 10 + 20), size=(300, 30), debug=debug, ), dui1.Text( ( 'Use this as reference for building' ' UIs with DocUI.' ' Its code lives at bauiv1lib.docuitest' ), scale=0.5, position=(0, -18 + 20), size=(600, 23), debug=debug, ), dui1.Image( 'nub', position=(0, -58 + 20), size=(60, 60) ), ], header_decorations_right=[ dui1.Text( 'HeaderRight', position=(0, 10 + 20), color=(1, 1, 1, 0.3), size=(150, 30), h_align=dui1.HAlign.RIGHT, debug=debug, ), ], title='Some Tests', buttons=[ dui1.Button( 'Browse', size=(120, 80), action=dui1.Browse(dui1.Request('/test2')), ), dui1.Button( 'Replace', size=(120, 80), action=dui1.Replace(dui1.Request('/test2')), ), dui1.Button( 'Close', size=(120, 80), action=dui1.Local(close_window=True), ), dui1.Button( 'Invalid\nRequest', size=(120, 80), action=dui1.Browse(dui1.Request('/invalidrequest')), ), dui1.Button( 'Immediate\nClientEffects', size=(120, 80), action=dui1.Local( immediate_client_effects=[ clfx.ScreenMessage( 'Hello From Immediate Client Effects', color=(0, 1, 0), ), clfx.PlaySound(clfx.Sound.CASH_REGISTER), clfx.Delay(1.0), clfx.ScreenMessage( '{"r":"successText"}', is_lstr=True, color=(0, 1, 0), ), clfx.PlaySound(clfx.Sound.CASH_REGISTER), ] ), ), dui1.Button( 'Response\nClientEffects', size=(120, 80), action=dui1.Browse( dui1.Request('/', args={'test_effects': True}) ), ), dui1.Button( 'Immediate\nLocalAction', size=(120, 80), action=dui1.Local( immediate_local_action='testaction', immediate_local_action_args={'testparam': 123}, ), ), dui1.Button( 'Response\nLocalAction', size=(120, 80), action=dui1.Browse( dui1.Request('/', args={'test_action': True}) ), ), ], ), dui1.ButtonRow( title='A Few More Tests', buttons=[ dui1.Button( 'Hide\nDebug' if debug else 'Show\nDebug', size=(120, 80), action=dui1.Replace( dui1.Request('/', args={'debug': not debug}) ), ), dui1.Button( 'Slow\nBrowse', size=(120, 80), action=dui1.Browse(dui1.Request('/slow')), ), dui1.Button( 'Slow\nReplace', size=(120, 80), action=dui1.Replace(dui1.Request('/slow')), ), dui1.Button( 'Timed\nActions', size=(120, 80), action=dui1.Browse(dui1.Request('/timedactions')), ), dui1.Button( 'Web\nGET', size=(120, 80), action=dui1.Browse(dui1.Request('/webtest/get')), ), dui1.Button( 'Web\nPOST', size=(120, 80), action=dui1.Browse( dui1.Request( '/webtest/post', method=dui1.RequestMethod.POST, ) ), ), dui1.Button( 'DisplayItems', size=(120, 80), action=dui1.Browse(dui1.Request('/displayitems')), ), dui1.Button( 'Empty\nPage', size=(120, 80), action=dui1.Browse(dui1.Request('/emptypage')), ), ], ), dui1.ButtonRow( title='Even More Tests', buttons=[ dui1.Button( 'Cloud-Msg\nGET', size=(120, 80), action=dui1.Browse( dui1.Request('/cloudmsgtest/get') ), ), dui1.Button( 'Cloud-Msg\nPOST', size=(120, 80), action=dui1.Browse( dui1.Request( '/cloudmsgtest/post', method=dui1.RequestMethod.POST, ) ), ), dui1.Button( 'Bounds\nTests', size=(120, 80), action=dui1.Browse(dui1.Request('/boundstests')), ), ], ), dui1.ButtonRow(title='Empty Row', buttons=[]), dui1.ButtonRow( title='Layout Tests', debug=debug, padding_left=5.0, buttons=[ dui1.Button( label='Test', size=(180, 200), decorations=[ dui1.Image( 'powerupPunch', position=(-70, 0), size=(40, 40), h_align=dui1.HAlign.LEFT, ), dui1.Image( 'powerupSpeed', position=(0, 75), size=(35, 35), v_align=dui1.VAlign.TOP, ), dui1.Text( 'TL', position=(-70, 75), size=(50, 50), h_align=dui1.HAlign.LEFT, v_align=dui1.VAlign.TOP, debug=debug, ), dui1.Text( 'TR', position=(70, 75), size=(50, 50), h_align=dui1.HAlign.RIGHT, v_align=dui1.VAlign.TOP, debug=debug, ), dui1.Text( 'BL', position=(-70, -75), size=(50, 50), h_align=dui1.HAlign.LEFT, v_align=dui1.VAlign.BOTTOM, debug=debug, ), dui1.Text( 'BR', position=(70, -75), size=(50, 50), h_align=dui1.HAlign.RIGHT, v_align=dui1.VAlign.BOTTOM, debug=debug, ), ], ), dui1.Button( label='Test2', 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. dui1.Button( label='Test', size=(180, 200), scale=0.6, padding_bottom=30, # Should nudge us up. debug=debug, # Show bounds. decorations=[ dui1.Image( 'powerupPunch', position=(-70, 0), size=(40, 40), h_align=dui1.HAlign.LEFT, ), dui1.Image( 'powerupSpeed', position=(0, 75), size=(35, 35), v_align=dui1.VAlign.TOP, ), dui1.Text( 'TL', position=(-70, 75), size=(50, 50), h_align=dui1.HAlign.LEFT, v_align=dui1.VAlign.TOP, debug=debug, ), dui1.Text( 'TR', position=(70, 75), size=(50, 50), h_align=dui1.HAlign.RIGHT, v_align=dui1.VAlign.TOP, debug=debug, ), dui1.Text( 'BL', position=(-70, -75), size=(50, 50), h_align=dui1.HAlign.LEFT, v_align=dui1.VAlign.BOTTOM, debug=debug, ), dui1.Text( 'BR', position=(70, -75), size=(50, 50), h_align=dui1.HAlign.RIGHT, v_align=dui1.VAlign.BOTTOM, debug=debug, ), ], ), # Testing custom button images and opacity. dui1.Button( label='Test3', texture='buttonSquareWide', padding_left=10.0, padding_right=10.0, color=(1, 1, 1, 0.3), size=(200, 100), ), # Testing image drawing vs bounds dui1.Button( label='BoundsTest', texture='white', color=(1, 1, 1, 0.3), size=(150, 100), debug=debug, ), ], ), dui1.ButtonRow( title='Long Row Test', subtitle='Look - a subtitle!', buttons=[ dui1.Button( size=(150, 100), decorations=[ dui1.Text( 'MaxWidthTest', position=(0, 25), size=(150 * 0.8, 32.0), flatness=1.0, shadow=0.0, debug=debug, ), dui1.Text( 'MaxHeightTest\nSecondLine', position=(0, -20), size=(150 * 0.8, 40), flatness=1.0, shadow=0.0, debug=debug, ), ], ), dui1.Button( size=(150, 100), decorations=[ dui1.Image( 'zoeIcon', position=(0, 0), size=(70, 70), tint_texture='zoeIconColorMask', tint_color=(1, 0, 0), tint2_color=(0, 1, 0), mask_texture='characterIconMask', ), ], ), dui1.Button( size=(150, 100), decorations=[ dui1.Image( 'bridgitPreview', position=(0, 10), size=(120, 60), mask_texture='mapPreviewMask', mesh_opaque='level_select_button_opaque', mesh_transparent=( 'level_select_button_transparent' ), ), ], ), dui1.Button(size=(150, 100)), dui1.Button(size=(150, 100)), dui1.Button(size=(150, 100)), dui1.Button(size=(150, 100)), dui1.Button(size=(150, 100)), dui1.Button(size=(150, 100)), dui1.Button(size=(150, 100)), dui1.Button(size=(150, 100)), dui1.Button( 'foo', size=(150, 100), scale=0.4, padding_left=100, padding_right=200, ), dui1.Button(size=(150, 100)), dui1.Button(size=(150, 100)), dui1.Button(size=(150, 100)), dui1.Button(size=(150, 100)), dui1.Button(size=(150, 100)), dui1.Button(size=(150, 100)), dui1.Button(size=(150, 100)), dui1.Button(size=(150, 100)), ], ), dui1.ButtonRow( buttons=[ dui1.Button( 'Row-With-No-Title Test', size=(300, 80), style=dui1.ButtonStyle.MEDIUM, color=(0.8, 0.8, 0.8, 1), icon='buttonPunch', icon_color=(0.5, 0.3, 1.0, 1.0), icon_scale=1.2, ), ], ), dui1.ButtonRow( title='Centered Content / Faded Title', title_color=(0.6, 0.6, 1.0, 0.3), title_flatness=1.0, title_shadow=1.0, subtitle='Testing Centered Title/Content', subtitle_color=(1.0, 0.5, 1.0, 0.5), subtitle_flatness=1.0, subtitle_shadow=0.0, center_content=True, center_title=True, buttons=[ dui1.Button( 'Hello There!', size=(200, 120), color=(0.7, 0.7, 0.9, 1), ), ], ), ], ), ) # Include some client effects if they ask. if request.args.get('test_effects', False): response.client_effects = [ clfx.ScreenMessage( 'Hello From Response Client Effects', color=(0, 1, 0) ), clfx.PlaySound(clfx.Sound.CASH_REGISTER), clfx.Delay(1.0), clfx.ScreenMessage( '{"r":"successText"}', is_lstr=True, color=(0, 1, 0) ), clfx.PlaySound(clfx.Sound.CASH_REGISTER), ] # Include a local-action if they ask. if request.args.get('test_action', False): response.local_action = 'testaction' response.local_action_args = {'testparam': 234} return response def _test_page_empty( request: bacommon.docui.v1.Request, ) -> bacommon.docui.v1.Response: import bacommon.docui.v1 as dui1 del request # Unused. return dui1.Response(page=dui1.Page(title='EmptyPage', rows=[])) def _test_page_display_items( request: bacommon.docui.v1.Request, ) -> bacommon.docui.v1.Response: """Testing display-items.""" from bacommon.bs import ClassicChestAppearance, ClassicChestDisplayItem import bacommon.docui.v1 as dui1 import bacommon.displayitem as ditm # Show some specific debug bits if they ask us to. debug = bool(request.args.get('debug', False)) def _make_test_button( scale: float, wrapper: ditm.Wrapper, ) -> dui1.Button: # See how this looks when unrecognized (relying on wrapper info # only). uwrapper = copy.deepcopy(wrapper) uwrapper.item = ditm.Unknown() return dui1.Button( size=(300, 400), scale=scale, decorations=[ dui1.DisplayItem( wrapper=wrapper, style=dui1.DisplayItemStyle.FULL, position=(-62, 100), size=(120, 120), debug=debug, ), dui1.DisplayItem( wrapper=uwrapper, style=dui1.DisplayItemStyle.FULL, position=(62, 100), size=(120, 120), debug=debug, ), dui1.DisplayItem( wrapper=wrapper, style=dui1.DisplayItemStyle.COMPACT, position=(-55, -20), size=(80, 80), debug=debug, ), dui1.DisplayItem( wrapper=uwrapper, style=dui1.DisplayItemStyle.COMPACT, position=(55, -20), size=(80, 80), debug=debug, ), dui1.DisplayItem( wrapper=wrapper, style=dui1.DisplayItemStyle.ICON, position=(-55, -120), size=(100, 80), debug=debug, ), dui1.DisplayItem( wrapper=uwrapper, style=dui1.DisplayItemStyle.ICON, position=(55, -120), size=(100, 80), debug=debug, ), ], ) return dui1.Response( page=dui1.Page( padding_left=20, padding_right=20, title='DisplayItems', rows=[ dui1.ButtonRow( debug=debug, padding_left=-10, title='Display Item Tests', subtitle=( 'top=FULL, center=COMPACT, bottom=ICON;' ' left=regular, right=unknown' ), buttons=[ _make_test_button( 1.0, ditm.Wrapper.for_item(ditm.Tickets(count=213)), ), _make_test_button( 0.47, ditm.Wrapper.for_item(ditm.Tickets(count=213)), ), _make_test_button( 1.0, ditm.Wrapper.for_item( ClassicChestDisplayItem( appearance=ClassicChestAppearance.L3 ) ), ), _make_test_button( 1.0, ditm.Wrapper.for_item(ditm.Tokens(count=3)), ), _make_test_button( 1.0, ditm.Wrapper.for_item(ditm.Tokens(count=1414287)), ), _make_test_button( 1.0, ditm.Wrapper.for_item(ditm.Test()), ), ], ), dui1.ButtonRow( buttons=[ dui1.Button( 'Hide Debug' if debug else 'Show Debug', style=dui1.ButtonStyle.MEDIUM, size=(240, 60), color=(0.6, 0.4, 0.8, 1.0), action=dui1.Replace( dui1.Request( request.path, args={'debug': not debug} ) ), ) ], ), ], ) ) def _test_bounds( request: bacommon.docui.v1.Request, ) -> bacommon.docui.v1.Response: import bacommon.docui.v1 as dui1 del request # Unused. def _nm(style: dui1.ButtonStyle) -> str: return f'{type(style).__name__}.{style.name}' return dui1.Response( page=dui1.Page( title='BoundsTests', rows=[ dui1.ButtonRow( title=_nm(dui1.ButtonStyle.SQUARE), buttons=[ dui1.Button('Hello', size=(300, 300), debug=True), dui1.Button('Hello', size=(200, 200), debug=True), dui1.Button('Hello', size=(100, 100), debug=True), ], ), dui1.ButtonRow( title=_nm(dui1.ButtonStyle.SQUARE_WIDE), buttons=[ dui1.Button( 'Hello', size=(400, 200), style=dui1.ButtonStyle.SQUARE_WIDE, debug=True, ), dui1.Button( 'Hello', size=(200, 250), style=dui1.ButtonStyle.SQUARE_WIDE, debug=True, ), dui1.Button( 'Hello', size=(60, 100), style=dui1.ButtonStyle.SQUARE_WIDE, debug=True, ), ], ), dui1.ButtonRow( title='(background texture)', buttons=[ dui1.Button( 'Hello', size=(300, 300), texture='white', color=(1, 0, 0, 0.3), debug=True, ), dui1.Button( 'Hello', size=(200, 200), texture='white', color=(1, 0, 0, 0.3), debug=True, ), dui1.Button( 'Hello', size=(100, 100), texture='white', color=(1, 0, 0, 0.3), debug=True, ), ], ), dui1.ButtonRow( title=_nm(dui1.ButtonStyle.TAB), buttons=[ dui1.Button( 'Hello', size=(400, 100), style=dui1.ButtonStyle.TAB, debug=True, ), dui1.Button( 'Hello', size=(200, 50), style=dui1.ButtonStyle.TAB, debug=True, ), dui1.Button( 'Hello', size=(100, 60), style=dui1.ButtonStyle.TAB, debug=True, ), ], ), dui1.ButtonRow( title=_nm(dui1.ButtonStyle.LARGER), buttons=[ dui1.Button( 'Hello', size=(500, 100), style=dui1.ButtonStyle.LARGER, debug=True, ), dui1.Button( 'Hello', size=(200, 50), style=dui1.ButtonStyle.LARGER, debug=True, ), dui1.Button( 'Hello', size=(100, 60), style=dui1.ButtonStyle.LARGER, debug=True, ), ], ), dui1.ButtonRow( title=_nm(dui1.ButtonStyle.LARGE), buttons=[ dui1.Button( 'Hello', size=(400, 100), style=dui1.ButtonStyle.LARGE, debug=True, ), dui1.Button( 'Hello', size=(200, 50), style=dui1.ButtonStyle.LARGE, debug=True, ), dui1.Button( 'Hello', size=(100, 60), style=dui1.ButtonStyle.LARGE, debug=True, ), ], ), dui1.ButtonRow( title=_nm(dui1.ButtonStyle.MEDIUM), buttons=[ dui1.Button( 'Hello', size=(300, 100), style=dui1.ButtonStyle.MEDIUM, debug=True, ), dui1.Button( 'Hello', size=(200, 50), style=dui1.ButtonStyle.MEDIUM, debug=True, ), dui1.Button( 'Hello', size=(100, 60), style=dui1.ButtonStyle.MEDIUM, debug=True, ), ], ), dui1.ButtonRow( title=_nm(dui1.ButtonStyle.SMALL), buttons=[ dui1.Button( 'Hello', size=(200, 100), style=dui1.ButtonStyle.SMALL, debug=True, ), dui1.Button( 'Hello', size=(200, 50), style=dui1.ButtonStyle.SMALL, debug=True, ), dui1.Button( 'Hello', size=(100, 60), style=dui1.ButtonStyle.SMALL, debug=True, ), ], ), dui1.ButtonRow( title=_nm(dui1.ButtonStyle.BACK), buttons=[ dui1.Button( 'Hello', size=(200, 100), style=dui1.ButtonStyle.BACK, debug=True, ), dui1.Button( 'Hello', size=(200, 50), style=dui1.ButtonStyle.BACK, debug=True, ), dui1.Button( 'Hello', size=(100, 60), style=dui1.ButtonStyle.BACK, debug=True, ), ], ), dui1.ButtonRow( title=_nm(dui1.ButtonStyle.BACK_SMALL), buttons=[ dui1.Button( 'Hello', size=(200, 100), style=dui1.ButtonStyle.BACK_SMALL, debug=True, ), dui1.Button( 'Hello', size=(200, 50), style=dui1.ButtonStyle.BACK_SMALL, debug=True, ), dui1.Button( 'Hello', size=(100, 60), style=dui1.ButtonStyle.BACK_SMALL, debug=True, ), ], ), ], ) ) # 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