-
-
Notifications
You must be signed in to change notification settings - Fork 798
Added numberinput to web and textual backends. #3299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
36f7072
05d1ad6
576b0f8
5be0757
517efff
599d84f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| The web and Textual backends now provide a NumberInput widget. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| from travertino.size import at_least | ||
|
|
||
| from textual.validation import Number | ||
| from textual.widgets import Input as TextualInput | ||
|
|
||
| from .base import Widget | ||
|
|
||
|
|
||
| class TogaInput(TextualInput): | ||
| def __init__(self, impl): | ||
| super().__init__() | ||
| self.interface = impl.interface | ||
| self.impl = impl | ||
|
|
||
| def on_input_changed(self, event: TextualInput.Changed) -> None: | ||
| self.interface.on_change() | ||
|
|
||
| def on_input_blurred(self, event: TextualInput.Blurred): | ||
| if self.impl.get_value() == "-": | ||
| self.impl.set_value("0") | ||
|
|
||
| if self.impl.get_value() is not None and self.impl.min is not None: | ||
| if float(self.impl.get_value()) < self.impl.min: | ||
| self.impl.set_value(str(self.impl.min)) | ||
|
|
||
| if self.impl.get_value() is not None and self.impl.max is not None: | ||
| if float(self.impl.get_value()) > self.impl.max: | ||
| self.impl.set_value(str(self.impl.max)) | ||
|
|
||
|
|
||
| class NumberInput(Widget): | ||
| def create(self): | ||
| self.native = TogaInput(self) | ||
| self.native.type = "number" | ||
| self.min = None | ||
| self.max = None | ||
|
|
||
| def get_readonly(self): | ||
| return self.native.disabled | ||
|
|
||
| def set_readonly(self, value): | ||
| self.native.disabled = value | ||
|
|
||
| def get_placeholder(self): | ||
| return self.native.placeholder | ||
|
|
||
| def set_placeholder(self, value): | ||
| self.native.placeholder = value | ||
|
|
||
| def get_value(self): | ||
| if self.native.value == "" or self.native.value is None: | ||
| return None | ||
| else: | ||
| if self.native.value != "-": | ||
| return float(self.native.value) | ||
| else: | ||
| return self.native.value | ||
|
|
||
| def set_value(self, value): | ||
| try: | ||
| if value is None: | ||
| self.native.value = "" | ||
| else: | ||
| self.native.value = str(value) | ||
| except AttributeError: | ||
| self.native.value = "" | ||
|
|
||
| def set_step(self, step): | ||
| pass | ||
|
|
||
| def set_min_value(self, value): | ||
| self.min = value | ||
| self.native.validators = [ | ||
| Number(minimum=self.min), | ||
| ] | ||
|
|
||
| def set_max_value(self, value): | ||
| self.max = value | ||
| self.native.validators = [ | ||
| Number(maximum=self.max), | ||
| ] | ||
|
|
||
| @property | ||
| def width_adjustment(self): | ||
| return 2 | ||
|
|
||
| @property | ||
| def height_adjustment(self): | ||
| return 2 | ||
|
|
||
| def rehint(self): | ||
| self.interface.intrinsic.width = at_least(10) | ||
| self.interface.intrinsic.height = 3 | ||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,50 @@ | ||||
| from .base import Widget | ||||
|
|
||||
|
|
||||
| class NumberInput(Widget): | ||||
| def create(self): | ||||
| self._return_listener = None | ||||
| self.native = self._create_native_widget("sl-input") | ||||
| self.native.type = "number" | ||||
| self.native.value = None | ||||
| self.native.onblur = self.lost_focus | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have a convention of clearly naming native event handlers based on their native naming - so in this case, |
||||
|
|
||||
| def lost_focus(self, event): | ||||
| print(self.native.value == "-") | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like stray debugging code?
Suggested change
|
||||
| if self.native.value == "": | ||||
| self.native.value = None | ||||
|
|
||||
| if self.native.value is not None and self.native.min is not None: | ||||
| if float(self.native.value) < self.native.min: | ||||
| self.native.value = self.native.min | ||||
|
|
||||
| if self.native.value is not None and self.native.max is not None: | ||||
| if float(self.native.value) > self.native.max: | ||||
| self.native.value = self.native.max | ||||
|
|
||||
| def get_readonly(self, value): | ||||
| return self.native.readOnly | ||||
|
|
||||
| def set_readonly(self, value): | ||||
| self.native.readOnly = value | ||||
|
|
||||
| def set_step(self, step): | ||||
| self.native.step = step | ||||
|
|
||||
| def set_min_value(self, value): | ||||
| self.native.min = value | ||||
|
|
||||
| def set_max_value(self, value): | ||||
| self.native.max = value | ||||
|
|
||||
| def get_value(self): | ||||
| if self.native.value == "" or self.native.value is None: | ||||
| return None | ||||
| else: | ||||
| return float(self.native.value) | ||||
|
|
||||
| def set_value(self, value): | ||||
| self.native.value = value | ||||
|
|
||||
| def set_text_align(self, value): | ||||
| pass | ||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blurred definitely looks like the right feature here (side note... why they didn't call this "focus" I don't know, but whatever). However, it looks like it wasn't added until textual 2.0, so we need to bump the minimum Textual version in the pyproject.toml to accomodate this. There's no problem doing a version bump, as long as there's no other consequences - in my very quick testing, Textual 2.0 has some weird "on app close" logic that seemed to get in the way of actually exiting the app; Textual 3.0 works, but outputs:
to the console on exit, which suggests there's some sort of new exit handling required.
There's also a small code re-use issue here. The clipping logic is almost identical to the clipping logic in the core. It would be preferable to avoid duplicating that logic is possible; it would be preferable to factor out a "_clipped_decimal" utility method in the interface that does all the value normalisation, and then using that method in both the
valuesetter and theon_input_blurredimplementation for Textual.I suspect that might also reveal a couple of other cleaning issues (handling of
-->Nonerather than 0; and step clipping).