2

I develop a simple app using Textual framework. I have two widgets W1 and W2. W1 has a fixed height of 20 (lines). Now I want W2 to take up the rest of the vertical space. In css for a browser I would use calc(100vh - 20) but textual does not (yet) support this.

How can I achieve this dynamic (i.e. viewport-height-dependent) layout?

cknoll
  • 2,130
  • 4
  • 18
  • 34

1 Answers1

2

Your best approach here will be to use the fr dimension unit. This makes it easy to split a container up while retaining relative dimensions and it also mixes well with specific-value dimensions. So, in your code, it would make sense to say that one particular widget is simply height: 20 while the other is height: 1fr.

As a simple example:

from textual.app        import App, ComposeResult
from textual.containers import Vertical
from textual.widgets    import Header, Footer, Static

class HeightApp( App[ None ] ):

    CSS = """
    Static {
       border: round green;
    }

    #top {
        height: 20;
    }

    #bottom {
        height: 1fr;
    }
    """

    def compose( self ) -> ComposeResult:
        yield Header()
        with Vertical():
            yield Static( "This is 20 high", id="top" )
            yield Static( "This is the rest of the terminal high", id="bottom" )
        yield Footer()

if __name__ == "__main__":
    HeightApp().run()
davep
  • 106
  • 4
  • 1
    This is great. I was not aware that one could combine absolute and relative units like this and that `1fr` only relates to the available space. IMHO your solution would make a valuable enhancement of the documentation. – cknoll Mar 23 '23 at 19:33
  • I noticed that the proposed solution seems not to work (out of the box) if there is e.g. a `margin: 3;` attribute inside `#top {...}`. Is there a quick solution? – cknoll Mar 24 '23 at 12:07
  • 1
    The issue with `margin` is that it isn't part of the widget, and so, in effect, it displaces the layout. See [the Textual documentation for details of the box model](https://textual.textualize.io/guide/styles/#box-model), and especially this part: > the margin style adds space around a widget's border, which isn't technically part of the widget, but provides visual separation between widgets If it's just space you want around the top, you could look at `padding`, or wrap the `#top` inside another container to control layout. – davep Mar 30 '23 at 07:52