# Released under the MIT License. See LICENSE for details.
#
"""Dev-Console functionality."""
from __future__ import annotations
import os
from typing import TYPE_CHECKING, override
from dataclasses import dataclass
import logging
import _babase
if TYPE_CHECKING:
from typing import Callable, Any, Literal
class DevConsoleTab:
"""Defines behavior for a tab in the dev-console."""
[docs]
def refresh(self) -> None:
"""Called when the tab should refresh itself."""
[docs]
def request_refresh(self) -> None:
"""The tab can call this to request that it be refreshed."""
_babase.dev_console_request_refresh()
[docs]
def text(
self,
text: str,
pos: tuple[float, float],
h_anchor: Literal['left', 'center', 'right'] = 'center',
h_align: Literal['left', 'center', 'right'] = 'center',
v_align: Literal['top', 'center', 'bottom', 'none'] = 'center',
scale: float = 1.0,
) -> None:
"""Add a button to the tab being refreshed."""
assert _babase.app.devconsole.is_refreshing
_babase.dev_console_add_text(
text, pos[0], pos[1], h_anchor, h_align, v_align, scale
)
[docs]
def python_terminal(self) -> None:
"""Add a Python Terminal to the tab being refreshed."""
assert _babase.app.devconsole.is_refreshing
_babase.dev_console_add_python_terminal()
@property
def width(self) -> float:
"""Return the current tab width. Only call during refreshes."""
assert _babase.app.devconsole.is_refreshing
return _babase.dev_console_tab_width()
@property
def height(self) -> float:
"""Return the current tab height. Only call during refreshes."""
assert _babase.app.devconsole.is_refreshing
return _babase.dev_console_tab_height()
@property
def base_scale(self) -> float:
"""A scale value set depending on the app's UI scale.
Dev-console tabs can incorporate this into their UI sizes and
positions if they desire. This must be done manually however.
"""
assert _babase.app.devconsole.is_refreshing
return _babase.dev_console_base_scale()
class DevConsoleTabPython(DevConsoleTab):
"""The Python dev-console tab."""
@override
def refresh(self) -> None:
self.python_terminal()
class DevConsoleTabTest(DevConsoleTab):
"""Test dev-console tab."""
@override
def refresh(self) -> None:
import random
self.button(
f'FLOOP-{random.randrange(200)}',
pos=(10, 10),
size=(100, 30),
h_anchor='left',
label_scale=0.6,
call=self.request_refresh,
)
self.button(
f'FLOOP2-{random.randrange(200)}',
pos=(120, 10),
size=(100, 30),
h_anchor='left',
label_scale=0.6,
style='dark',
)
self.text(
'TestText',
scale=0.8,
pos=(15, 50),
h_anchor='left',
h_align='left',
v_align='none',
)
@dataclass
class DevConsoleTabEntry:
"""Represents a distinct tab in the dev-console."""
name: str
factory: Callable[[], DevConsoleTab]
class DevConsoleSubsystem:
"""Subsystem for wrangling the dev console.
The single instance of this class can be found at
babase.app.devconsole. The dev-console is a simple always-available
UI intended for use by developers; not end users. Traditionally it
is available by typing a backtick (`) key on a keyboard, but now can
be accessed via an on-screen button (see settings/advanced to enable
said button).
"""
def __init__(self) -> None:
# All tabs in the dev-console. Add your own stuff here via
# plugins or whatnot.
self.tabs: list[DevConsoleTabEntry] = [
DevConsoleTabEntry('Python', DevConsoleTabPython)
]
if os.environ.get('BA_DEV_CONSOLE_TEST_TAB', '0') == '1':
self.tabs.append(DevConsoleTabEntry('Test', DevConsoleTabTest))
self.is_refreshing = False
[docs]
def do_refresh_tab(self, tabname: str) -> None:
"""Called by the C++ layer when a tab should be filled out."""
assert _babase.in_logic_thread()
# FIXME: We currently won't handle multiple tabs with the same
# name. We should give a clean error or something in that case.
tab: DevConsoleTab | None = None
for tabentry in self.tabs:
if tabentry.name == tabname:
tab = tabentry.factory()
break
if tab is None:
logging.error(
'DevConsole got refresh request for tab'
" '%s' which does not exist.",
tabname,
)
return
self.is_refreshing = True
try:
tab.refresh()
finally:
self.is_refreshing = False