# Released under the MIT License. See LICENSE for details.
#
"""A minimal password-entry prompt dialog."""
from __future__ import annotations # Docs-generation hack.
from typing import TYPE_CHECKING, cast
import bauiv1 as bui
from bauiv1 import classicassets
if TYPE_CHECKING:
from typing import Callable
[docs]
class PasswordPromptWindow:
"""Small modal overlay window prompting for a password.
Calls ``on_result`` exactly once: the entered password on submit or
None on cancel (via the cancel button, back press, or an external
:meth:`dismiss`).
"""
def __init__(
self,
*,
description: str | bui.Lstr | bui.LangStr | None = None,
on_result: Callable[[str | None], None] | None = None,
):
ui = bui.app.ui_v1
# Make sure our widgets have globally unique ids.
self._id_prefix = ui.new_id_prefix('passwordprompt')
self._on_result = on_result
self._result_sent = False
if description is None:
description = classicassets.strings.gather.party_requires_password
width = 420.0
height = 200.0
uiscale = ui.uiscale
self._root_widget = bui.containerwidget(
size=(width, height),
transition='in_scale',
toolbar_visibility='menu_minimal_no_back',
parent=bui.get_special_widget('overlay_stack'),
scale=(
1.9
if uiscale is bui.UIScale.SMALL
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
),
darken_behind=True,
)
bui.textwidget(
parent=self._root_widget,
position=(width * 0.5, height - 40),
size=(0, 0),
h_align='center',
v_align='center',
text=description,
maxwidth=width * 0.9,
)
self._text_field = bui.textwidget(
parent=self._root_widget,
id=f'{self._id_prefix}|password',
editable=True,
size=(width - 80, 40),
position=(40, height - 110),
text='',
maxwidth=width - 100,
max_chars=100,
autoselect=True,
v_align='center',
password=True,
description=(
description
if isinstance(description, (str, bui.Lstr))
else description.evaluate()
),
on_return_press_call=self._submit,
)
cbtn = bui.buttonwidget(
parent=self._root_widget,
id=f'{self._id_prefix}|cancel',
autoselect=True,
position=(20, 20),
size=(150, 50),
label=classicassets.strings.ui.cancel,
on_activate_call=self._cancel,
)
okbtn = bui.buttonwidget(
parent=self._root_widget,
id=f'{self._id_prefix}|ok',
autoselect=True,
position=(width - 175, 20),
size=(150, 50),
label=classicassets.strings.ui.ok,
on_activate_call=self._submit,
)
bui.containerwidget(
edit=self._root_widget,
cancel_button=cbtn,
start_button=okbtn,
selected_child=self._text_field,
)
[docs]
def dismiss(self) -> None:
"""Externally dismiss the prompt (counts as a cancel). Idempotent."""
self._cancel()
def _send_result(self, result: str | None) -> None:
if self._result_sent:
return
self._result_sent = True
if self._on_result is not None:
self._on_result(result)
def _submit(self) -> None:
if not self._root_widget:
return
password = cast(str, bui.textwidget(query=self._text_field))
bui.containerwidget(edit=self._root_widget, transition='out_scale')
self._send_result(password)
def _cancel(self) -> None:
if self._root_widget:
bui.containerwidget(edit=self._root_widget, transition='out_scale')
self._send_result(None)
# 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