Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/textual/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -4903,12 +4903,13 @@ def _check_refresh(self) -> None:
self.call_later(self.update_node_styles)
if self._scroll_required:
self._scroll_required = False
if not self._layout_required:
if self.styles.keyline[0] != "none":
# TODO: Feels like a hack
# Perhaps there should be an explicit mechanism for backgrounds to refresh when scrolled?
self._set_dirty()
screen.post_message(messages.UpdateScroll())
if self.styles.keyline[0] != "none":
# A keyline's background follows its content scroll.
self._set_dirty()
# Layout sources may be held at the screen. Preserve the
# independent scroll intent; Screen owns whether actionable
# sources require full layout or only visible reflow.
screen.post_message(messages.UpdateScroll())
if self._repaint_required:
self._repaint_required = False
if self.display:
Expand Down
58 changes: 58 additions & 0 deletions tests/test_native_scene_size_publication.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,64 @@
from textual.widgets import Static


async def test_scroll_intent_survives_layout_and_held_sources():
from textual.screen import Screen

history = VerticalScroll(Static("\n".join(str(n) for n in range(50))))

class IntentScreen(Screen):
held = False

def __init__(self):
super().__init__()
self.admissions = []

def compose(self):
yield history

def _layout_mutation_roots(self):
return (history,) if self.held else ()

def _prepare_compositor_refresh(self):
return self._layout_mutation_roots()

def _refresh_layout(self, size=None, scroll=False):
self.admissions.append((
scroll, self._has_actionable_layout_requests(self._held_layout_requests())
))
super()._refresh_layout(size, scroll=scroll)

class IntentApp(App):
CSS = "VerticalScroll { height: 8; } Static { height: auto; }"

def get_default_screen(self):
return IntentScreen()

app = IntentApp()
async with app.run_test(size=(30, 12)) as pilot:
await pilot.pause()
screen = app.screen
original = screen._compositor.find_widget(history.children[0])
screen.held = True
screen.admissions.clear()
history.refresh(layout=True)
history.scroll_to(y=10, animate=False, immediate=True)
history._check_refresh()
await pilot.pause()
assert (True, False) in screen.admissions
assert screen._compositor.find_widget(history.children[0]) == original
assert screen._layout_widgets
screen.held = False
screen.admissions.clear()
history.refresh(layout=True)
history.scroll_to(y=15, animate=False, immediate=True)
history._check_refresh()
await pilot.pause()
assert (True, True) in screen.admissions
assert not screen._layout_widgets
assert screen._compositor.find_widget(history.children[0]).region.y == -15


class SizeReceipts:
def on_resize(self, event: events.Resize) -> None:
self.receipts.append((event.size, event.virtual_size, event.container_size))
Expand Down
Loading