diff --git a/src/toad/forms/editors.py b/src/toad/forms/editors.py index b5e410bf3..30b8af9db 100644 --- a/src/toad/forms/editors.py +++ b/src/toad/forms/editors.py @@ -101,9 +101,7 @@ def watch_feedback(self, text: str) -> None: self.post_message(self.Feedback(text)) -class BoolEditor(ValueEditor, FormCheckbox): - shows = (bool,) - +class BoolEditor(ValueEditor, FormCheckbox, shows=(bool,)): def control_arguments(self) -> dict[str, object]: return {"value": bool(self.field.value)} @@ -165,23 +163,19 @@ def paint(self) -> None: self.value = self.value_text(self.field.value) -class IntEditor(LineEditor): - shows = (int,) +class IntEditor(LineEditor, shows=(int,)): accepts = "integer" -class FloatEditor(LineEditor): - shows = (float,) +class FloatEditor(LineEditor, shows=(float,)): accepts = "number" -class StrEditor(LineEditor): - shows = (str,) +class StrEditor(LineEditor, shows=(str,)): accepts = "text" -class MultilineEditor(ValueEditor, UntintedTonePaint, FramedControl, TextArea): - shows = (Multiline,) +class MultilineEditor(ValueEditor, UntintedTonePaint, FramedControl, TextArea, shows=(Multiline,)): height = Scalar.from_number(10) padding = Spacing(0, 1, 0, 1) @@ -255,9 +249,7 @@ def paint(self) -> None: self.value = value if self.lists(value) else Select.NULL -class ChoicesEditor(SelectEditor): - shows = (AnnotationChoices,) - +class ChoicesEditor(SelectEditor, shows=(AnnotationChoices,)): def paint(self) -> None: with self.prevent(Select.Changed): self.set_options(self.options()) @@ -273,20 +265,16 @@ def options(self) -> Iterable[tuple[str, object]]: return ((self.choices.label(choice), choice) for choice in self.choices.choices()) -class EnumEditor(SelectEditor): - shows = (Enum,) - +class EnumEditor(SelectEditor, shows=(Enum,)): def options(self) -> Iterable[tuple[str, object]]: members: type[Enum] = resolve_annotated(self.field.annotation) # type: ignore[assignment] return ((format_enum_display(member), member) for member in members) -class OptionalEditor(ValueEditor, WidgetView, containers.VerticalGroup): +class OptionalEditor(ValueEditor, WidgetView, containers.VerticalGroup, shows=(Optional,)): """A field that may hold nothing: a box says whether it holds a value, and the editor of that value is shown while it does.""" - shows = (Optional,) - def bind_control(self) -> None: self.holds_value = FormCheckbox("Set", value=self.field.value is not None) self.member = ValueEditor.declared_for(self.field.member) diff --git a/src/toad/forms/form_view.py b/src/toad/forms/form_view.py index a46e70ffd..2947eb18d 100644 --- a/src/toad/forms/form_view.py +++ b/src/toad/forms/form_view.py @@ -54,7 +54,7 @@ def paint_foreground(self): return self.app.colours.colour(PrimaryColour) -class FieldView(WidgetView, containers.VerticalGroup, DeclaredViews): +class FieldView(DeclaredViews, WidgetView, containers.VerticalGroup): """One parameter of a form, shown the way its formbridge kind asks for.""" def __init__(self, field: DeclaredField) -> None: @@ -84,11 +84,9 @@ def filter(self, term: str) -> bool: raise NotImplementedError -class ValueView(FieldView): +class ValueView(FieldView, shows=(ParameterInfo,)): """A field with one value: its label, its help and the editor for its kind.""" - shows = (ParameterInfo,) - CHANGED_MARK = " ●" def __init__(self, field: DeclaredField) -> None: @@ -132,11 +130,9 @@ def mark(self, changed: bool) -> None: ) -class GroupView(FocusWithinView, FieldView): +class GroupView(FocusWithinView, FieldView, shows=(DirectDataclassInfo,)): """A nested dataclass: a heading and the views of its fields.""" - shows = (DirectDataclassInfo,) - padding = Spacing(0, 1, 0, 1) @property diff --git a/src/toad/screens/settings.py b/src/toad/screens/settings.py index 51029e470..7f27830ef 100644 --- a/src/toad/screens/settings.py +++ b/src/toad/screens/settings.py @@ -27,11 +27,9 @@ from toad.app_node import ToadNode -class SchemeEditor(SelectEditor, ToadNode): +class SchemeEditor(SelectEditor, ToadNode, shows=(SchemeName,)): """A colour scheme's name, picked from the schemes the running app has.""" - shows = (SchemeName,) - def options(self) -> Iterable[tuple[str, object]]: return ((name, name) for name in self.app.colours.schemes) diff --git a/src/toad/widgets/comms_command_dialog.py b/src/toad/widgets/comms_command_dialog.py index bb75cd38e..01b33099b 100644 --- a/src/toad/widgets/comms_command_dialog.py +++ b/src/toad/widgets/comms_command_dialog.py @@ -23,7 +23,7 @@ from toad.widgets.checkbox import Checkbox from toad.forms.editors import MultilineEditor, ValueEditor from toad.forms.form_view import InlineFormView -from formbridge.forms.declared_fields import Multiline +from formbridge.forms.declared_fields import DeclaredMetadata, Multiline from toad.native_style import AutoWidth, EllipsisOverflow, SideMargin from toad.screens.modal import CenteredScreen, DismissibleScreen, ModalPanel, ModalScreen, ShadedScreen from toad.widget_actions import WidgetAction @@ -32,7 +32,7 @@ @dataclass(eq=False) -class PreparedChoices(AnnotationChoices): +class PreparedChoices(AnnotationChoices, DeclaredMetadata): """The catalog last prepared for a draft, not another copy of its value.""" parameter: ChoiceTargetField @@ -42,24 +42,22 @@ def choices(self): def label(self, choice): return next((label for label, value in self.parameter.choices if value == choice), 'Choose…') - -ValueEditor.shown_by[PreparedChoices] = lambda field: ValueEditor.showing( - (*field.metadata(PreparedChoices).parameter.editor_declarations, AnnotationChoices) -)(field) + def declares(self): + """The prepared parameter names its own editors first; any choices editor follows.""" + yield from self.parameter.editor_declarations + yield from super().declares() class CommandText(Multiline): """A command parameter of several lines; the command form shows it five rows tall.""" -class CommandTextEditor(MultilineEditor): - shows = (CommandText,) +class CommandTextEditor(MultilineEditor, shows=(CommandText,)): height = Scalar.from_number(5) -class CatalogField(ValueEditor, Button): +class CatalogField(ValueEditor, Button, shows=(ModelConfigOption,)): """The shared model selector edits the form's canonical draft value.""" - shows = (ModelConfigOption,) def bind_control(self): self.paint() @@ -244,7 +242,6 @@ def draft_changed(self, paths): group='command-catalogs', exclusive=True) - def arguments(self): return {name: self.state.get_resolved_value(name) for name in self.form.values} diff --git a/tests/textual/test_settings_screen.py b/tests/textual/test_settings_screen.py index b3d811e7f..7570b1e26 100644 --- a/tests/textual/test_settings_screen.py +++ b/tests/textual/test_settings_screen.py @@ -29,8 +29,7 @@ class Shouted: """Declares text that is kept in capitals.""" -class ShoutedEditor(editors.StrEditor): - shows = (Shouted,) +class ShoutedEditor(editors.StrEditor, shows=(Shouted,)): def read(self, text: str) -> object: return text.upper()