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
8 changes: 8 additions & 0 deletions src/textual/css/_style_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,7 @@ def __init__(
refresh_parent: bool = False,
display: bool = False,
pointer: bool = False,
geometry: bool = True,
) -> None:
self._valid_values = valid_values
self._default = default
Expand All @@ -842,6 +843,13 @@ def __init__(
self._refresh_parent = refresh_parent
self._display = display
self._pointer = pointer
self._geometry = geometry

def affects_geometry(self, previous: object | None, value: object | None) -> bool:
# Geometry inputs and scheduled layout are distinct: native wrapping,
# positioning and constraints can change geometry without _layout.
# Undeclared enum effects remain conservative.
return self._geometry

def __set_name__(self, owner: StylesBase, name: str) -> None:
self.name = name
Expand Down
2 changes: 1 addition & 1 deletion src/textual/css/styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ class StylesBase:
"""Padding added to left and right of lines."""

pointer: StringEnumProperty[PointerShape] = StringEnumProperty(
VALID_POINTER, "default", pointer=True
VALID_POINTER, "default", pointer=True, geometry=False
)
"""Set the pointer (cursor) shape when the mouse is over this widget.

Expand Down
37 changes: 37 additions & 0 deletions tests/css/test_programmatic_style_changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,43 @@
from textual.widgets import Label


async def test_pointer_shape_preserves_native_measurement_and_unknown_renderers():
"""Cursor publication has no native size input; custom renderers retain it."""
from textual.content import Content
from textual.widgets import Static

class PointerContent(Static):
def render(self):
return Content(self.styles.pointer)

native, custom = Label("original row"), PointerContent()
app = App()
async with app.run_test(size=(40, 12)) as pilot:
await app.mount(native, custom)
await pilot.pause()
await pilot.hover(native)
before = native.region
measurement, geometry = native._layout_updates, native._geometry_revision
custom_measurement = custom._layout_updates
native.styles.pointer = "text"
assert app.screen._pointer_shape == "text"
assert native._layout_updates == measurement
assert native._geometry_revision == geometry
custom.styles.pointer = "crosshair"
assert custom._layout_updates > custom_measurement
await pilot.pause()
assert native.region == before
assert app.screen.get_widget_at(before.x, before.y)[0] is native
# Raw source writes and stylesheet replacement share the descriptor.
native._inline_styles.set_rule("pointer", "pointer")
native._css_styles.replace_rules(dict(native._css_styles.get_rules(), pointer="help"))
assert native._layout_updates == measurement
assert native._geometry_revision == geometry
# Not every unscheduled enum is paint-only: wrapping remains a size input.
native.styles.text_wrap = "nowrap"
assert native._layout_updates > measurement


async def test_style_publication_preserves_source_and_content_lifetimes():
"""A frame request must not retire measurements twice or hide new content."""
from textual.css.scalar import Scalar
Expand Down
Loading