Setting an offset causes child widgets not to appear, and screen can't be reused!

There are two problems with the following script:

import logging

from rich.console import Console

from textual.app import App, ComposeResult
from textual.containers import Container
from textual.screen import Screen
from textual.widgets import Label, Footer

logger = logging.getLogger(__name__)

console = Console()

class About(Screen):
    def compose(self) -> ComposeResult:
        self.content = Container(
            Label("My Application"),
            Label("An example application"),
            Label("Copyright (C) 2022 Me"),
            Label("https://www.mysite.com/"),
            id="about",
        )
        yield self.content

    def on_mount(self):
        x = (console.width - 30) // 2
        y = (console.height - 11) // 2
        logger.debug('Offset: (%s, %s)', x, y)
        # self.content.styles.offset = (x, y)

class MyApp(App):
    BINDINGS = [
        ('ctrl+a', 'show_about', 'About'),
        ('ctrl+b', 'hide_about', 'Hide About')
    ]

    CSS = '''
    #about {
        content-align: center middle;
        border: solid brown;
        background: ansi_white;
        height: 11;
        width: 30;
    }
    #about Label {
        text-align: center;
        margin-top: 1;
        color: ansi_red;
    }
    About {
    }
    '''

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.about_showing = False

    def compose(self):
        yield Container(Label('Just some text'))
        yield Footer()

    def on_mount(self):
        self.about = About()

    def action_show_about(self):
        if not self.about_showing:
            self.push_screen(self.about)
            self.about_showing = True

    def action_hide_about(self):
        if self.about_showing:
            self.pop_screen()
            self.about_showing = False

def main():
    logging.basicConfig(level=logging.DEBUG,
                        filename='app.log',
                        filemode='w',
                        format='%(levelname)-8s %(message)s')
    app = MyApp()
    app.run()


if __name__ == '__main__':
    main()

Problem No. 1

The above script produces this output, as expected, when Ctrl+A is pressed:

However, if I uncomment this line

# self.content.styles.offset = (x, y)

and re-run, then the container appears centered, but its contents aren’t visible!

Can anyone explain why?

Problem No. 2

The About screen can’t be reused, apparently, and that isn’t documented as far as I can tell. Even though I save the instance in an app attribute and use push_screen() and pop_screen() to show and hide the screen, the screen never appears again if Ctrl+A is pressed. However, if I re-instantiate it each time before showing it, that works. Why can’t the first instance be reused?

I couldn’t figure out how to make the dynamic offset thing work either. Seems like a bug. If you remove the width and height settings in the CSS then it “works” but of course the box is the wrong size.

2nd question, I suspect that pop_screen essentially de-allocates the object. remove() on a widget does something kind of similar, you can’t just put it back into the parent. What I usually do in this case is push both screens and then use switch_screen(screen_name) to switch instead. Below is an example of how I generally do it

import logging

from rich.console import Console

from textual.app import App, ComposeResult
from textual.containers import Container
from textual.screen import Screen
from textual.widgets import Label, Footer

logger = logging.getLogger(__name__)

console = Console()


class About(Screen):
    DEFAULT_CSS = '''
    #about {
        content-align: center middle;
        border: solid brown;
        background: ansi_white;
        height: 11;
        width: 30;
    }

    #about Label {
        text-align: center;
        margin-top: 1;
        color: ansi_red;
    }
    About {
    }
    '''


    def compose(self) -> ComposeResult:
        self.content = Container(
            Label("My Application"),
            Label("An example application"),
            Label("Copyright (C) 2022 Me"),
            Label("https://www.mysite.com/"),
            id="about",
        )
        yield self.content

    def on_mount(self):
        x = (console.width - 30) // 2
        y = (console.height - 11) // 2
        logger.debug('Offset: (%s, %s)', x, y)
        # self.content.styles.offset = (x, y)


class Main(Screen):
    def compose(self):
        yield Container(Label('Just some text'))
        yield Footer()


class MyApp(App):
    BINDINGS = [
        ('ctrl+a', 'show_about', 'About'),
        ('ctrl+b', 'hide_about', 'Hide About')
    ]

    SCREENS = {
        "main": Main(),
        "about": About(),
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def action_show_about(self):
        self.switch_screen("about")

    def action_hide_about(self):
        self.switch_screen("main")

    def on_mount(self):
        self.push_screen("about")
        self.push_screen("main")


def main():
    logging.basicConfig(level=logging.DEBUG,
                        filename='app.log',
                        filemode='w',
                        format='%(levelname)-8s %(message)s')
    app = MyApp()
    app.run()


if __name__ == '__main__':
    main()

1 Like

Yes, I think it’s this.