From 3e2b9e40be0d14465b4374e0a0a5cfc7e224d0fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=BBebrak?= <mzebrak@syncad.com> Date: Fri, 7 Mar 2025 14:47:50 +0100 Subject: [PATCH] Allow for no context in Form/FormScreen --- clive/__private/ui/forms/form.py | 29 +++++++++++++----------- clive/__private/ui/forms/form_context.py | 12 ++++++++++ clive/__private/ui/forms/form_screen.py | 11 +++++---- 3 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 clive/__private/ui/forms/form_context.py diff --git a/clive/__private/ui/forms/form.py b/clive/__private/ui/forms/form.py index a1ee9ec61c..954e87107f 100644 --- a/clive/__private/ui/forms/form.py +++ b/clive/__private/ui/forms/form.py @@ -7,8 +7,9 @@ from queue import Queue from typing import TYPE_CHECKING, Any from clive.__private.core.commands.abc.command import Command -from clive.__private.core.contextual import ContextT, Contextual +from clive.__private.core.contextual import ContextualHolder from clive.__private.ui.clive_screen import CliveScreen +from clive.__private.ui.forms.form_context import FormContextT, NoContext if TYPE_CHECKING: from clive.__private.ui.forms.form_screen import FormScreenBase @@ -16,38 +17,37 @@ if TYPE_CHECKING: PostAction = Command | Callable[[], Any] -class Form(Contextual[ContextT], CliveScreen[None]): +class Form(ContextualHolder[FormContextT], CliveScreen[None]): MINIMUM_SCREEN_COUNT = 2 # Rationale: it makes no sense to have only one screen in the form def __init__(self) -> None: self._current_screen_index = 0 - self._screen_types: list[type[FormScreenBase[ContextT]]] = [*list(self.compose_form())] + self._screen_types: list[type[FormScreenBase[FormContextT]]] = [*list(self.compose_form())] assert len(self._screen_types) >= self.MINIMUM_SCREEN_COUNT, "Form must have at least 2 screens" - self._rebuild_context() self._post_actions = Queue[PostAction]() - super().__init__() + super().__init__(self._build_context()) @abstractmethod - def compose_form(self) -> Iterator[type[FormScreenBase[ContextT]]]: + def compose_form(self) -> Iterator[type[FormScreenBase[FormContextT]]]: """Yield screens types in the order they should be displayed.""" - @abstractmethod - def _rebuild_context(self) -> None: - """Create brand new fresh context.""" - @property - def screens_types(self) -> list[type[FormScreenBase[ContextT]]]: + def screens_types(self) -> list[type[FormScreenBase[FormContextT]]]: return self._screen_types @property - def current_screen_type(self) -> type[FormScreenBase[ContextT]]: + def current_screen_type(self) -> type[FormScreenBase[FormContextT]]: return self._screen_types[self._current_screen_index] - def on_mount(self) -> None: + async def on_mount(self) -> None: assert self._current_screen_index == 0 + await self.initialize() self._push_current_screen() + async def initialize(self) -> None: + """Do actions that should be executed before the first form is displayed.""" + def next_screen(self) -> None: if not self._check_valid_range(self._current_screen_index + 1): return @@ -83,6 +83,9 @@ class Form(Contextual[ContextT], CliveScreen[None]): else: action() + def _build_context(self) -> FormContextT: + return NoContext() # type: ignore[return-value] + def _push_current_screen(self) -> None: self.app.push_screen(self.current_screen_type(self)) diff --git a/clive/__private/ui/forms/form_context.py b/clive/__private/ui/forms/form_context.py new file mode 100644 index 0000000000..1fe6e80e7f --- /dev/null +++ b/clive/__private/ui/forms/form_context.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from typing_extensions import TypeVar + +from clive.__private.core.contextual import Context + + +class NoContext(Context): + """A class that signals that there is no context.""" + + +FormContextT = TypeVar("FormContextT", bound=Context, default=NoContext) diff --git a/clive/__private/ui/forms/form_screen.py b/clive/__private/ui/forms/form_screen.py index 10f0bc7c7f..8dea9780c0 100644 --- a/clive/__private/ui/forms/form_screen.py +++ b/clive/__private/ui/forms/form_screen.py @@ -10,8 +10,9 @@ from textual.message import Message from textual.reactive import var from clive.__private.core.constants.tui.bindings import NEXT_SCREEN_BINDING_KEY, PREVIOUS_SCREEN_BINDING_KEY -from clive.__private.core.contextual import ContextT, Contextual +from clive.__private.core.contextual import Contextual from clive.__private.ui.clive_screen import CliveScreen +from clive.__private.ui.forms.form_context import FormContextT from clive.__private.ui.forms.navigation_buttons import NextScreenButton, PreviousScreenButton from clive.__private.ui.widgets.inputs.clive_input import CliveInput @@ -22,17 +23,17 @@ if TYPE_CHECKING: BackScreenModes = Literal["back_to_unlock", "nothing_to_back", "back_to_previous"] -class FormScreenBase(CliveScreen, Contextual[ContextT]): - def __init__(self, owner: Form[ContextT]) -> None: +class FormScreenBase(CliveScreen, Contextual[FormContextT]): + def __init__(self, owner: Form[FormContextT]) -> None: self._owner = owner super().__init__() @property - def context(self) -> ContextT: + def context(self) -> FormContextT: return self._owner.context -class FormScreen(FormScreenBase[ContextT], ABC): +class FormScreen(FormScreenBase[FormContextT], ABC): BINDINGS = [ Binding("escape", "previous_screen", "Previous screen", show=False), Binding(NEXT_SCREEN_BINDING_KEY, "next_screen", "Next screen"), -- GitLab