textual icon indicating copy to clipboard operation
textual copied to clipboard

Keyline rendering appears intermittent under scrolling.

Open holdenweb opened this issue 5 months ago • 5 comments

Have you checked closed issues? (https://github.com/Textualize/textual/issues?q=is%3Aissue+is%3Aclosed) yes

Have you checked against the most recent version of Textual? (https://pypi.org/search/?q=textual) yes

The bug

Please give a brief but clear explanation of the issue. If you can, include a complete working example that demonstrates the bug. Check it can run without modifications.

The following simple program demonstrates oddities in rendering as the container scrolls.

from textual.app import App, ComposeResult
from textual.widgets import Input
from textual.containers import Horizontal, Vertical, VerticalScroll

class MyApp(App):

    DEFAULT_CSS = """\
#my-container {
    keyline: heavy blue;
    Vertical {
        margin: 1;
        width: 1fr;
    height: 3;
    }
}
"""
    def compose(self) -> ComposeResult:
        with VerticalScroll(id="my-container"):
            for i in range(10):
                yield Vertical(Input(valid_empty=False, id=f"test-{i}", value="x"))

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

The initial display is sane. Image

Pressing the <down> key, however, does not redraw correctly. Image

A mouse click in the window at that stage triggers a correct redraw. Also please note that with a Vertical height of 3, the window paints correctly every 4 keypresses. Adding one to the height means 5 keypresses are required, and so on.

textual diagnose
<!-- This is valid Markdown, please paste the following directly in to a GitHub issue -->
# Textual Diagnostics

## Versions

| Name    | Value  |
|---------|--------|
| Textual | 3.5.0  |
| Rich    | 14.0.0 |

## Python

| Name           | Value                                                         |
|----------------|---------------------------------------------------------------|
| Version        | 3.12.8                                                        |
| Implementation | CPython                                                       |
| Compiler       | Clang 19.1.6                                                  |
| Executable     | /Users/sholden/Projects/Python/textual-forms/.venv/bin/python |

## Operating System

| Name    | Value                                                                                                  |
|---------|--------------------------------------------------------------------------------------------------------|
| System  | Darwin                                                                                                 |
| Release | 24.5.0                                                                                                 |
| Version | Darwin Kernel Version 24.5.0: Tue Apr 22 19:48:46 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T8103 |

## Terminal

| Name                 | Value              |
|----------------------|--------------------|
| Terminal Application | iTerm.app (3.5.14) |
| TERM                 | rxvt               |
| COLORTERM            | truecolor          |
| FORCE_COLOR          | *Not set*          |
| NO_COLOR             | *Not set*          |

## Rich Console options

| Name           | Value               |
|----------------|---------------------|
| size           | width=80, height=20 |
| legacy_windows | False               |
| min_width      | 1                   |
| max_width      | 80                  |
| is_terminal    | False               |
| encoding       | utf-8               |
| max_height     | 20                  |
| justify        | None                |
| overflow       | None                |
| no_wrap        | False               |
| highlight      | None                |
| markup         | None                |
| height         | None                |

holdenweb avatar Jul 02 '25 10:07 holdenweb

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 project is developed and maintained by Will McGugan. Consider sponsoring Will's work on this project (and others).

This is an automated reply, generated by FAQtory

github-actions[bot] avatar Jul 02 '25 10:07 github-actions[bot]

Can confirm I get the same issue in Windows Terminal. Here is a video. Also it does this if you just grab the scroll bar with the mouse and drag it, or use mouse wheel. Scrolling in general seems to cause it.

https://github.com/user-attachments/assets/04ce153d-a6da-4696-886f-61f138ba0735

edward-jazzhands avatar Jul 02 '25 11:07 edward-jazzhands

Anecdotal evidence from Discord suggests this issue is probably within textual.

holdenweb avatar Jul 02 '25 11:07 holdenweb

I think the problem is that scrolling won't cause any refresh/repaint of the container? After the initial render, the keylines won't be updated unless the container changes size (which does cause a repaint).

I haven't dived into this yet, so in case it helps someone else fix this, here's where the keylines are rendered:

https://github.com/Textualize/textual/blob/03979fc629579919d29c10a8ed6e10974fc05c4c/src/textual/widget.py#L4185-L4187

TomJGooding avatar Jul 04 '25 16:07 TomJGooding

Definitely a bug. Generally contains don't need to be re-rendered since they have plain backgrounds. Keylines are the exception.

In the meantime, putting the keylines in a nested containers is a workaround:

from textual.app import App, ComposeResult
from textual.widgets import Input
from textual.containers import Horizontal, Vertical, VerticalScroll, VerticalGroup


class MyApp(App):

    DEFAULT_CSS = """\
#my-container {
    
    Vertical {
        margin: 1;
        width: 1fr;
    height: 3;
    }
    .with-keylines {
    keyline: heavy blue;
    }
}
"""

    def compose(self) -> ComposeResult:
        with VerticalScroll(id="my-container"):
            with VerticalGroup(classes="with-keylines"):
                for i in range(10):
                    yield Vertical(Input(valid_empty=False, id=f"test-{i}", value="x"))


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

willmcgugan avatar Jul 06 '25 07:07 willmcgugan