mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
fix scrollbar not updating after auto size change
This commit is contained in:
@@ -146,7 +146,7 @@ class BasicApp(App, css_path="basic.css"):
|
||||
|
||||
def key_t(self):
|
||||
# Pressing "t" toggles the content of the TweetBody widget, from a long "Lorem ipsum..." to a shorter one.
|
||||
tweet_body = self.screen.query("TweetBody").first()
|
||||
tweet_body = self.query("TweetBody").first()
|
||||
tweet_body.short_lorem = not tweet_body.short_lorem
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ from rich.style import Style
|
||||
from . import errors
|
||||
from .geometry import Region, Offset, Size
|
||||
|
||||
from ._profile import timer
|
||||
from ._loop import loop_last
|
||||
from ._segment_tools import line_crop
|
||||
from ._types import Lines
|
||||
@@ -555,8 +556,7 @@ class Compositor:
|
||||
def _assemble_chops(
|
||||
cls, chops: list[dict[int, list[Segment] | None]]
|
||||
) -> list[list[Segment]]:
|
||||
|
||||
# Pretty sure we don't need to sort the bucket items
|
||||
"""Combine chops in to lines."""
|
||||
segment_lines: list[list[Segment]] = [
|
||||
sum(
|
||||
[line for line in bucket.values() if line is not None],
|
||||
@@ -608,6 +608,8 @@ class Compositor:
|
||||
chops: list[dict[int, list[Segment] | None]]
|
||||
chops = [fromkeys(cut_set) for cut_set in cuts]
|
||||
|
||||
cut_segments: Iterable[list[Segment]]
|
||||
|
||||
# Go through all the renders in reverse order and fill buckets with no render
|
||||
renders = self._get_renders(crop)
|
||||
intersection = Region.intersection
|
||||
@@ -615,22 +617,29 @@ class Compositor:
|
||||
for region, clip, lines in renders:
|
||||
render_region = intersection(region, clip)
|
||||
|
||||
if not (render_region and crop.overlaps(render_region)):
|
||||
continue
|
||||
|
||||
for y, line in zip(render_region.y_range, lines):
|
||||
if not is_rendered_line(y):
|
||||
continue
|
||||
|
||||
chops_line = chops[y]
|
||||
if all(chops_line):
|
||||
continue
|
||||
|
||||
first_cut, last_cut = render_region.x_extents
|
||||
final_cuts = [cut for cut in cuts[y] if (last_cut >= cut >= first_cut)]
|
||||
|
||||
if len(final_cuts) == 2:
|
||||
if len(final_cuts) <= 2:
|
||||
# Two cuts, which means the entire line
|
||||
cut_segments = [line]
|
||||
else:
|
||||
render_x = render_region.x
|
||||
relative_cuts = [cut - render_x for cut in final_cuts]
|
||||
_, *cut_segments = divide(line, relative_cuts)
|
||||
relative_cuts = [cut - render_x for cut in final_cuts[1:]]
|
||||
cut_segments = divide(line, relative_cuts)
|
||||
|
||||
# Since we are painting front to back, the first segments for a cut "wins"
|
||||
chops_line = chops[y]
|
||||
for cut, segments in zip(final_cuts, cut_segments):
|
||||
if chops_line[cut] is None:
|
||||
chops_line[cut] = segments
|
||||
@@ -639,10 +648,15 @@ class Compositor:
|
||||
render_lines = self._assemble_chops(chops)
|
||||
return LayoutUpdate(render_lines, screen_region)
|
||||
else:
|
||||
# It may be possible to do this without the line crop
|
||||
crop_y, crop_y2 = crop.y_extents
|
||||
render_lines = self._assemble_chops(chops[crop_y:crop_y2])
|
||||
render_spans = [
|
||||
(y, x1, line_crop(render_lines[y - crop_y], x1, x2))
|
||||
(
|
||||
y,
|
||||
x1,
|
||||
line_crop(render_lines[y - crop_y], x1, x2),
|
||||
)
|
||||
for y, x1, x2 in spans
|
||||
]
|
||||
return SpansUpdate(render_spans, crop_y2)
|
||||
|
||||
@@ -5,10 +5,10 @@ from typing import Iterator, overload, TYPE_CHECKING
|
||||
import rich.repr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .dom import DOMNode
|
||||
from .widget import Widget
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
@rich.repr.auto(angular=True)
|
||||
class NodeList:
|
||||
"""
|
||||
A container for widgets that forms one level of hierarchy.
|
||||
@@ -19,7 +19,8 @@ class NodeList:
|
||||
|
||||
def __init__(self) -> None:
|
||||
# The nodes in the list
|
||||
self._nodes: list[DOMNode] = []
|
||||
self._nodes: list[Widget] = []
|
||||
self._nodes_set: set[Widget] = set()
|
||||
# Increments when list is updated (used for caching)
|
||||
self._updates = 0
|
||||
|
||||
@@ -35,29 +36,31 @@ class NodeList:
|
||||
def __len__(self) -> int:
|
||||
return len(self._nodes)
|
||||
|
||||
def __contains__(self, widget: DOMNode) -> bool:
|
||||
def __contains__(self, widget: Widget) -> bool:
|
||||
return widget in self._nodes
|
||||
|
||||
def _append(self, widget: DOMNode) -> None:
|
||||
if widget not in self._nodes:
|
||||
def _append(self, widget: Widget) -> None:
|
||||
if widget not in self._nodes_set:
|
||||
self._nodes.append(widget)
|
||||
self._nodes_set.add(widget)
|
||||
self._updates += 1
|
||||
|
||||
def _clear(self) -> None:
|
||||
del self._nodes[:]
|
||||
self._updates += 1
|
||||
if self._nodes:
|
||||
self._nodes.clear()
|
||||
self._nodes_set.clear()
|
||||
self._updates += 1
|
||||
|
||||
def __iter__(self) -> Iterator[DOMNode]:
|
||||
def __iter__(self) -> Iterator[Widget]:
|
||||
return iter(self._nodes)
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: int) -> DOMNode:
|
||||
def __getitem__(self, index: int) -> Widget:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: slice) -> list[DOMNode]:
|
||||
def __getitem__(self, index: slice) -> list[Widget]:
|
||||
...
|
||||
|
||||
def __getitem__(self, index: int | slice) -> DOMNode | list[DOMNode]:
|
||||
assert self._nodes is not None
|
||||
def __getitem__(self, index: int | slice) -> Widget | list[Widget]:
|
||||
return self._nodes[index]
|
||||
|
||||
@@ -266,6 +266,14 @@ class StylesBase(ABC):
|
||||
spacing = self.padding + self.border.spacing + self.margin
|
||||
return spacing
|
||||
|
||||
@property
|
||||
def auto_dimensions(self) -> bool:
|
||||
"""Check if width or height are set to 'auto'."""
|
||||
has_rule = self.has_rule
|
||||
return (has_rule("width") and self.width.is_auto) or (
|
||||
has_rule("height") and self.height.is_auto
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def has_rule(self, rule: str) -> bool:
|
||||
"""Check if a rule is set on this Styles object.
|
||||
|
||||
@@ -305,8 +305,6 @@ class Stylesheet:
|
||||
},
|
||||
)
|
||||
self.replace_rules(node, node_rules, animate=animate)
|
||||
if isinstance(node, Widget):
|
||||
node._refresh_scrollbars()
|
||||
|
||||
@classmethod
|
||||
def replace_rules(
|
||||
|
||||
@@ -176,9 +176,9 @@ class ClientHandler:
|
||||
message_time = time()
|
||||
if (
|
||||
last_message_time is not None
|
||||
and message_time - last_message_time > 1
|
||||
and message_time - last_message_time > 0.5
|
||||
):
|
||||
# Print a rule if it has been longer than a second since the last message
|
||||
# Print a rule if it has been longer than half a second since the last message
|
||||
self.service.console.rule()
|
||||
self.service.console.print(
|
||||
DevConsoleLog(
|
||||
|
||||
@@ -186,7 +186,7 @@ class Region(NamedTuple):
|
||||
"""Create a Region from the union of other regions.
|
||||
|
||||
Args:
|
||||
regions (Iterable[Region]): One or more regions.
|
||||
regions (Sequence[Region]): One or more regions.
|
||||
|
||||
Returns:
|
||||
Region: A Region that encloses all other regions.
|
||||
@@ -240,7 +240,7 @@ class Region(NamedTuple):
|
||||
The end value is non inclusive.
|
||||
|
||||
Returns:
|
||||
tuple[int, int]: [description]
|
||||
tuple[int, int]: Pair of x coordinates (row numbers).
|
||||
"""
|
||||
return (self.x, self.x + self.width)
|
||||
|
||||
@@ -251,7 +251,7 @@ class Region(NamedTuple):
|
||||
The end value is non inclusive.
|
||||
|
||||
Returns:
|
||||
tuple[int, int]: [description]
|
||||
tuple[int, int]: Pair of y coordinates (line numbers).
|
||||
"""
|
||||
return (self.y, self.y + self.height)
|
||||
|
||||
|
||||
@@ -623,19 +623,16 @@ class Widget(DOMNode):
|
||||
horizontal_scrollbar_thickness = self.scrollbar_size_horizontal
|
||||
vertical_scrollbar_thickness = self.scrollbar_size_vertical
|
||||
|
||||
print(self, horizontal_scrollbar_thickness, vertical_scrollbar_thickness)
|
||||
|
||||
if self.styles.scrollbar_gutter == "stable":
|
||||
# Let's _always_ reserve some space, whether the scrollbar is actually displayed or not:
|
||||
show_vertical_scrollbar = True
|
||||
vertical_scrollbar_thickness = self.styles.scrollbar_size_vertical
|
||||
|
||||
if show_horizontal_scrollbar and show_vertical_scrollbar:
|
||||
print(1, region)
|
||||
(region, _, _, _) = region.split(
|
||||
-vertical_scrollbar_thickness, -horizontal_scrollbar_thickness
|
||||
-vertical_scrollbar_thickness,
|
||||
-horizontal_scrollbar_thickness,
|
||||
)
|
||||
print(2, region)
|
||||
elif show_vertical_scrollbar:
|
||||
region, _ = region.split_vertical(-vertical_scrollbar_thickness)
|
||||
elif show_horizontal_scrollbar:
|
||||
@@ -915,6 +912,9 @@ class Widget(DOMNode):
|
||||
self._content_height_cache = (None, 0)
|
||||
self.set_dirty()
|
||||
self._repaint_required = True
|
||||
if isinstance(self.parent, Widget) and self.styles.auto_dimensions:
|
||||
self.parent.refresh(layout=True)
|
||||
|
||||
self.check_idle()
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
|
||||
Reference in New Issue
Block a user