# Released under the MIT License. See LICENSE for details.
#
"""Dev tools settings, as a doc-ui page.
A client-local doc-ui domain like the other settings pages: the page
is authored here, its controls mirror the config in typed page state,
and every control is a typed local action.
"""
from __future__ import annotations # Docs-generation hack.
from dataclasses import dataclass, replace
from enum import Enum
from typing import TYPE_CHECKING, Annotated, override, assert_never
from efro.dataclassio import ioprepped, IOAttrs
import bacommon.docui.v2 as dui2
from bacommon.docui.presets import section_button
from bacommon.docui.routes import (
DocUIRoute,
DocUILocalActionBase,
DocUIState,
PressSound,
family_members,
)
import bauiv1 as bui
from bauiv1 import _classicassets, _commonassets
from bauiv1lib.docui import TypedDocUIController
if TYPE_CHECKING:
from typing import Literal
from bacommon.docui import DocUIResponse
from bacommon.langstr import LangStrSpec
from bauiv1lib.docui import DocUILocalAction
_devstrs = _classicassets.strings.settings.dev_tools
_SHOW_BUTTON_KEY = 'Show Dev Console Button'
_BUTTON_SIZE_KEY = 'Dev Console Button Size'
_BUTTON_OPACITY_KEY = 'Dev Console Button Opacity'
_BUTTON_STYLE_KEY = 'Dev Console Button Style'
_BUTTON_POS_KEYS = (
'Dev Console Button Pos X',
'Dev Console Button Pos Y',
'Dev Console Button Anchor',
)
# How often a slider drag re-applies the config (the button updates
# live).
_DRAG_INTERVAL = 0.1
[docs]
@ioprepped
@dataclass
class Root(DevToolsRoute, path='/'):
"""The dev tools page."""
AnyDevToolsRoute = Root
[docs]
@ioprepped
@dataclass
class CreateUserSystemScripts(DevToolsLocalAction, name='create_scripts'):
"""Copy the system scripts out to the user dir for editing."""
[docs]
@ioprepped
@dataclass
class DeleteUserSystemScripts(DevToolsLocalAction, name='delete_scripts'):
"""Remove the user copy of the system scripts (after confirming)."""
[docs]
@override
@classmethod
def get_press_sound(cls) -> PressSound:
return PressSound.SWISH
AnyDevToolsLocalAction = (
ApplyShowButton
| ApplyButtonSetting
| ResetButton
| CreateUserSystemScripts
| DeleteUserSystemScripts
)
def _preload_modules() -> None:
"""Import what our actions use here, off the logic thread."""
# pylint: disable=cyclic-import
import babase.modutils as _unused1
from bauiv1lib import confirm as _unused2
def _style_from_config() -> DevConsoleButtonStyle:
try:
return DevConsoleButtonStyle(bui.app.config.resolve(_BUTTON_STYLE_KEY))
except ValueError:
return DevConsoleButtonStyle.GREY
def _style_label(style: DevConsoleButtonStyle) -> LangStrSpec:
match style:
case DevConsoleButtonStyle.GREY:
return _devstrs.style_grey.spec
case DevConsoleButtonStyle.GREEN:
return _devstrs.style_green.spec
case DevConsoleButtonStyle.PURPLE:
return _devstrs.style_purple.spec
case DevConsoleButtonStyle.HOWDY:
return _devstrs.style_howdy.spec
case _:
assert_never(style)
def _page() -> dui2.Response:
"""Build the page (called in a background thread)."""
config = bui.app.config
state = DevToolsState(
show_dev_console_button=bool(config.resolve(_SHOW_BUTTON_KEY)),
dev_console_button_size=float(config.resolve(_BUTTON_SIZE_KEY)),
dev_console_button_opacity=float(config.resolve(_BUTTON_OPACITY_KEY)),
dev_console_button_style=_style_from_config(),
)
enabled = state.show_dev_console_button
apply = ApplyButtonSetting().local(default_sound=False)
apply_drag = ApplyButtonSetting(commit=False).local(default_sound=False)
button_rows: list[dui2.Row] = [
DevToolsState.checkbox_row(
lambda s: s.show_dev_console_button,
label=_devstrs.enable.spec,
on_change=ApplyShowButton().local(default_sound=False),
),
DevToolsState.slider_row(
lambda s: s.dev_console_button_size,
min_value=0.5,
max_value=4.0,
increment=0.1,
decimals=1,
label=_devstrs.size.spec,
on_drag=apply_drag,
drag_interval=_DRAG_INTERVAL,
on_change=apply,
disabled=not enabled,
),
DevToolsState.slider_row(
lambda s: s.dev_console_button_opacity,
min_value=0.0,
max_value=1.0,
increment=0.1,
decimals=1,
label=_devstrs.opacity.spec,
on_drag=apply_drag,
drag_interval=_DRAG_INTERVAL,
on_change=apply,
disabled=not enabled,
),
DevToolsState.choice_row(
lambda s: s.dev_console_button_style,
choice_label=_style_label,
label=_devstrs.style.spec,
on_change=apply,
disabled=not enabled,
),
# The reset hangs right under the controls it goes with. (Fill
# rows span the column as the control rows above do. Paddings
# reproduce the button stacks these replaced -- 8/6 there, but
# a scrolling row lifts its content 6 units, so effectively
# 2/12 -- leaving spacing unchanged.)
dui2.ButtonRow(
layout=dui2.ButtonRowLayout.FILL,
buttons=[
replace(
section_button(
_commonassets.strings.actions.reset.spec,
ResetButton().local(),
),
disabled=not enabled,
)
],
padding_top=2.0,
padding_bottom=12.0,
),
]
rows: list[dui2.Row] = [
dui2.Section(
title=_devstrs.dev_console_button.spec,
subtitle=_devstrs.drag_to_reposition.spec,
title_align=dui2.HAlign.CENTER,
rows=button_rows,
),
dui2.Section(
title=_devstrs.user_system_scripts.spec,
title_align=dui2.HAlign.CENTER,
rows=[
dui2.ButtonRow(
layout=dui2.ButtonRowLayout.FILL,
buttons=[
section_button(
_devstrs.create_scripts.spec,
CreateUserSystemScripts().local(),
),
section_button(
_devstrs.delete_scripts.spec,
DeleteUserSystemScripts().local(),
),
],
padding_top=2.0,
padding_bottom=12.0,
),
],
),
]
return dui2.Response(
page=dui2.Page(
title=_devstrs.title.spec,
rows=rows,
state=state.encode(),
center_vertically=True,
)
)
def _apply_show_button(context: DocUILocalAction) -> None:
state = context.state(DevToolsState)
if state is None:
return
cfg = bui.app.config
cfg[_SHOW_BUTTON_KEY] = state.show_dev_console_button
cfg.apply_and_commit()
def _apply_button_setting(context: DocUILocalAction, *, commit: bool) -> None:
"""Write the changed size, opacity or style to the config."""
state = context.state(DevToolsState)
if state is None:
return
cfg = bui.app.config
cfg[_BUTTON_SIZE_KEY] = state.dev_console_button_size
cfg[_BUTTON_OPACITY_KEY] = state.dev_console_button_opacity
cfg[_BUTTON_STYLE_KEY] = state.dev_console_button_style.value
if commit:
cfg.apply_and_commit()
else:
cfg.apply()
def _reset_button() -> None:
# Drop our stored size, opacity, style, and custom position; applying
# then reverts the button to its default look in its default docked
# spot.
cfg = bui.app.config
cfg.pop(_BUTTON_SIZE_KEY, None)
cfg.pop(_BUTTON_OPACITY_KEY, None)
cfg.pop(_BUTTON_STYLE_KEY, None)
for key in _BUTTON_POS_KEYS:
cfg.pop(key, None)
cfg.apply_and_commit()
# 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