diff --git a/CHANGELOG.md b/CHANGELOG.md index f039e06a2b..8f29d01ec1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Breaking change: Widget.notify and App.notify now return None https://github.com/Textualize/textual/pull/3275 - App.unnotify is now private (renamed to App._unnotify) https://github.com/Textualize/textual/pull/3275 - `Markdown.load` will now attempt to scroll to a related heading if an anchor is provided https://github.com/Textualize/textual/pull/3244 +- `ProgressBar` explicitly supports being set back to its indeterminate state https://github.com/Textualize/textual/pull/3286 ## [0.36.0] - 2023-09-05 diff --git a/src/textual/_types.py b/src/textual/_types.py index 03f83f619d..b1ad7972f3 100644 --- a/src/textual/_types.py +++ b/src/textual/_types.py @@ -26,6 +26,10 @@ def post_message(self, message: "Message") -> bool: ... +class UnusedParameter: + """Helper type for a parameter that isn't specified in a method call.""" + + SegmentLines = List[List["Segment"]] CallbackType = Union[Callable[[], Awaitable[None]], Callable[[], None]] """Type used for arbitrary callables used in callbacks.""" diff --git a/src/textual/types.py b/src/textual/types.py index b768c424c4..024d388f24 100644 --- a/src/textual/types.py +++ b/src/textual/types.py @@ -9,6 +9,7 @@ CallbackType, IgnoreReturnCallbackType, MessageTarget, + UnusedParameter, WatchCallbackType, ) from .actions import ActionParseResult @@ -29,5 +30,6 @@ "MessageTarget", "NoActiveAppError", "RenderStyles", + "UnusedParameter", "WatchCallbackType", ] diff --git a/src/textual/widgets/_progress_bar.py b/src/textual/widgets/_progress_bar.py index 617d390892..ec8c1b22cb 100644 --- a/src/textual/widgets/_progress_bar.py +++ b/src/textual/widgets/_progress_bar.py @@ -8,16 +8,19 @@ from rich.style import Style -from textual.geometry import clamp - +from .._types import UnusedParameter from ..app import ComposeResult, RenderResult from ..containers import Horizontal +from ..geometry import clamp from ..reactive import reactive from ..renderables.bar import Bar as BarRenderable from ..timer import Timer from ..widget import Widget from ..widgets import Label +UNUSED = UnusedParameter() +"""Sentinel for method signatures.""" + class Bar(Widget, can_focus=False): """The bar portion of the progress bar.""" @@ -276,7 +279,6 @@ class ProgressBar(Widget, can_focus=False): """The total number of steps associated with this progress bar, when known. The value `None` will render an indeterminate progress bar. - Once `total` is set to a numerical value, it cannot be set back to `None`. """ percentage: reactive[float | None] = reactive[Optional[float]](None) """The percentage of progress that has been completed. @@ -398,6 +400,7 @@ def advance(self, advance: float = 1) -> None: ```py progress_bar.advance(10) # Advance 10 steps. ``` + Args: advance: Number of steps to advance progress by. """ @@ -406,30 +409,28 @@ def advance(self, advance: float = 1) -> None: def update( self, *, - total: float | None = None, - progress: float | None = None, - advance: float | None = None, + total: None | float | UnusedParameter = UNUSED, + progress: float | UnusedParameter = UNUSED, + advance: float | UnusedParameter = UNUSED, ) -> None: """Update the progress bar with the given options. - Options only affect the progress bar if they are not `None`. - Example: ```py progress_bar.update( total=200, # Set new total to 200 steps. - progress=None, # This has no effect. + progress=50, # Set the progress to 50 (out of 200). ) ``` Args: - total: New total number of steps (if not `None`). - progress: Set the progress to the given number of steps (if not `None`). - advance: Advance the progress by this number of steps (if not `None`). + total: New total number of steps. + progress: Set the progress to the given number of steps. + advance: Advance the progress by this number of steps. """ - if total is not None: + if not isinstance(total, UnusedParameter): self.total = total - if progress is not None: + if not isinstance(progress, UnusedParameter): self.progress = progress - if advance is not None: + if not isinstance(advance, UnusedParameter): self.progress += advance diff --git a/tests/test_progress_bar.py b/tests/test_progress_bar.py index 64b034817d..bc7f799196 100644 --- a/tests/test_progress_bar.py +++ b/tests/test_progress_bar.py @@ -79,7 +79,7 @@ def test_update_total(): assert pb.total == 1000 pb.update(total=None) - assert pb.total == 1000 + assert pb.total is None pb.update(total=100) assert pb.total == 100 @@ -119,6 +119,15 @@ def test_update(): assert pb.progress == 50 +def test_go_back_to_indeterminate(): + pb = ProgressBar() + + pb.total = 100 + assert pb.percentage == 0 + pb.total = None + assert pb.percentage is None + + @pytest.mark.parametrize( ["show_bar", "show_percentage", "show_eta"], [