Textualize / textual

The lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.
https://textual.textualize.io/
MIT License
25.55k stars 785 forks source link

Repair broken embedded rich terminal example. #4117

Closed traveller1011 closed 8 months ago

traveller1011 commented 9 months ago

Part 1/2: Broken rich terminal example

Heads up - one of the embedded rich terminals examples (in the Animation section of the online Textual Guide) is seemingly malfunctioning.

I figure it may be worth looking into in case the root cause is something systemic that affects other rich terminal examples (none I saw though).

For these reasons it seems plausible a systemic problem could be afoot.

The problem is here:

https://textual.textualize.io/guide/animation/#__tabbed_1_3

The symptoms are

image

Part 2/2 - offsetting animate calls results in no animation at all

I don't think this is a bug. But nor did I see any remark in the documentation. And since we are talking about animations ill briefly share.

When you animate a button to become invisible over 2 second duration, then immediately reverse than animation to full opacity over another 2 second duration, no animation happens at all.

See the comments in the on_mount() method below:

class MenuSideBar(Container):
    CSS_PATH = MAIN_APP_STYLES

    button_events: Button

    def compose(self) -> ComposeResult:
        with VerticalScroll(id="sidebar_vscroll"):
            yield Button("Settings", id="btn_settings")
            yield Button("Accounts", id="btn_accounts")
            yield Button("Strategies", id="btn_strategies")
            yield Button("Instruments", id="btn_instruments")

            # randomly caching one button into self (to animate it for fun)
            self.button_events = Button("Events", id="btn_events")
            yield self.button_events

            yield Button("Charts", id="btn_charts")
            yield Button("System", id="btn_system")
            yield Button("Developer", id="btn_developer")
            yield Button("Bootup App", id="btn_start_background_services")
            yield Button("Exit", id="btn_exit")

    def on_mount(self):
        # TEXTUAL TEAM - see next two lines
        self.button_events.styles.animate("opacity", value=0.0, duration=2.0)     # <<< this works great running alone
        self.button_events.styles.animate("opacity", value=1.0, duration=2.0)     # <<< but add another animate here, and then no animation happens at all

I seriously am loving Textual, including the docs. Thanks you team Textual.

github-actions[bot] commented 9 months ago

We found the following entries in the FAQ which you may find helpful:

Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.

This is an automated reply, generated by FAQtory

davep commented 9 months ago

On point 2: it's because you're essentially overriding the first animation with the second. This is where you'd use on_complete. For example:

from functools import partial

from textual.app import App, ComposeResult
from textual.widgets import Button

class HideThenShowApp(App[None]):

    def compose(self) -> ComposeResult:
        yield Button("Watch me disappear then appear!")

    def on_mount(self) -> None:
        self.query_one(Button).styles.animate(
            "opacity",
            value=0.0,
            duration=2.0,
            on_complete=partial(
                self.query_one(Button).styles.animate,
                "opacity",
                value=1.0,
                duration=2.0
            )
        )

if __name__ == "__main__":
    HideThenShowApp().run()

Remember: the call to animate isn't blocking.

TomJGooding commented 9 months ago

Good spot regarding the broken examples in the docs! Just to clarify in case there's any confusion, these are only screenshots rather than "embedded terminals".

This has been reported and fixed once before, but there must have been a regression after changes to the code to generate the screenshots.

traveller1011 commented 9 months ago

Thank you both for writing back. Regarding point 2 animation-- i did figure out to use a callback several hours later... And instantly felt dumb reflecting about (this) earlier GH issue :)

    async def on_blur(self, _: events.Blur) -> None:
        # ....
        self.styles.animate("background", value="green", duration=1.0, on_complete=self._restore_orig_background_color)
        self.notify(f"Successfully updated {save_field} with new value: {new_value}")

    def _restore_orig_background_color(self):
        self.styles.animate("background", value=self._orig_styles_background, duration=1.0)

... what i did not figure out was your nifty use of partial, cheers to that idea.

ok i must return to building.

good afternoon and thanks again!

TomJGooding commented 8 months ago

I spotted the PR didn't link to this issue, but the animation example will be fixed by #4226 once the updated docs have been deployed.

davep commented 8 months ago

As identified by Tom above, this is fixed by #4226 -- confirmed locally; just needs the docs to be published. Closing as done.

github-actions[bot] commented 8 months ago

Don't forget to star the repository!

Follow @textualizeio for Textual updates.