textual icon indicating copy to clipboard operation
textual copied to clipboard

Unable to scroll widget visible

Open rodrigogiraoserrao opened this issue 3 years ago • 5 comments

While working on #2031 I found out that in some situations, you can't scroll a widget into visibility.

It seems that what you cannot do is scroll into visibility a widget that is a sub-widget of a compound widget.

I'm not sure if this is a bug or just a limitation. It would be nice if I could scroll to sub-widgets. On the other hand, if we consider a compound widget to be a unit, it also makes sense that we don't allow scrolling to specific parts of that unit.

Consider the app below; the compound widget MyCustomWidget contains two labels, one very tall and another one with a specific ID. Pressing T should scroll to that last label (to the bottom of the app) but it doesn't.

MWE
from textual.app import App, ComposeResult
from textual.containers import VerticalScroll
from textual.widgets import Label, Static


class MyCustomWidget(Static):
    def compose(self) -> ComposeResult:
        yield Label(("|\n" * 100)[:-1])
        yield Label("CAN'T GET HERE", id="target")


class MyApp(App):
    def compose(self) -> ComposeResult:
        with VerticalScroll():
            yield MyCustomWidget()

    def key_t(self) -> None:
        self.query_one("#target").scroll_visible()


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

rodrigogiraoserrao avatar Mar 30 '23 15:03 rodrigogiraoserrao

Upon further thought, I'm not sure if this is a bug or just a limitation. The title, prose, and labels, of the issue have been edited to reflect that.

rodrigogiraoserrao avatar Mar 31 '23 06:03 rodrigogiraoserrao

That should work. I am disappointed it didn't!

willmcgugan avatar Apr 06 '23 09:04 willmcgugan

Don't forget to star the repository!

Follow @textualizeio for Textual updates.

github-actions[bot] avatar Apr 06 '23 10:04 github-actions[bot]

This still doesn't work in some situations. The example below contains a couple of levels of nesting.

Run the app below, scroll all the way down manually, then press either s or t. The app scrolls a bit but it doesn't scroll enough to show the coloured lines that say "second" or "third", respectively.

App
from itertools import cycle

from textual.app import App, ComposeResult
from textual.containers import VerticalScroll
from textual.widgets import Label, Static


NAMES = [
    "Paul Atreidies",
    "Duke Leto Atreides",
    "Lady Jessica",
    "Gurney Halleck",
    "Baron Vladimir Harkonnen",
    "Glossu Rabban",
    "Chani",
    "Silgar",
]


class MyCustomWidget(Static):
    def __init__(self, id):
        self.__id = id
        super().__init__()

    def compose(self) -> ComposeResult:
        yield Label(("|\n" * 10)[:-1])
        blue = "[blue]@@@@@@@@@@@@@@@@@@@@@@@[/]\n" * 2
        red = f"[red]========={self.__id}==========[/]\n" * 3
        text = (blue + red + blue)[:-1]
        yield Label(text, id=self.__id)
        yield Label(("|\n" * 1)[:-1])


class MyApp(App):
    CSS = """
    #vertical {
        height: 20;
    }
    VerticalScroll {
        border: round $primary;
    }
    """

    def compose(self) -> ComposeResult:
        with VerticalScroll():
            for _, name in zip(range(50), cycle(NAMES)):
                yield Label(name)
            yield MyCustomWidget("first")
            with VerticalScroll():
                for _, name in zip(range(50), cycle(NAMES)):
                    yield Label(name)
                yield MyCustomWidget("second")
                with VerticalScroll(id="vertical"):
                    yield Label("[yellow]Vertical here[/]")
                    for _, name in zip(range(50), cycle(NAMES)):
                        yield Label(name)
                    yield MyCustomWidget("third")
                    for _, name in zip(range(30), cycle(NAMES)):
                        yield Label(name)
                    yield Label("[yellow]end of v[/]")
                for _, name in zip(range(100), cycle(NAMES)):
                    yield Label(name)
            for _, name in zip(range(50), cycle(NAMES)):
                yield Label(name)

    def key_f(self) -> None:
        self.query_one("#first").scroll_visible()

    def key_s(self) -> None:
        self.query_one("#second").scroll_visible()

    def key_t(self) -> None:
        self.query_one("#third").scroll_visible()

    def key_v(self) -> None:
        self.query_one("#vertical").scroll_visible()


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

rodrigogiraoserrao avatar Apr 10 '23 10:04 rodrigogiraoserrao

I think this same issue (or something very similar) has bit someone on Discord this morning. When there's nesting involved, the margin of the parent seems to not be taken into account. In the example below, if you press "x", our scrolling seems to be off-by-8, and the container has a margin-top of 8.

from textual.app import App, ComposeResult
from textual.containers import VerticalScroll, Container
from textual.widgets import Button


class ScrollVisibleMargin(App):
    CSS = """
    Container {
        height: auto;
        margin-top: 8;
        border: wide red;
    }
    """

    def compose(self) -> ComposeResult:
        with VerticalScroll():
            with Container():
                for index in range(1, 51):
                    yield Button(f"Hello, world! ({index})", id=f"b{index}")

    def key_x(self):
        button_twenty = self.query_one("#b26")
        button_twenty.scroll_visible()


app = ScrollVisibleMargin()
if __name__ == "__main__":
    app.run()

https://discord.com/channels/1026214085173461072/1033754296224841768/1164118381973217362

darrenburns avatar Oct 18 '23 10:10 darrenburns

Original MRE appears to work. Darren's issue still seems to be present.

willmcgugan avatar Jul 10 '24 10:07 willmcgugan

Don't forget to star the repository!

Follow @textualizeio for Textual updates.

github-actions[bot] avatar Jul 10 '24 13:07 github-actions[bot]