fix scrollbar not updating after auto size change

This commit is contained in:
Will McGugan
2022-06-06 11:42:24 +01:00
parent 51e29c5a10
commit d69205d59f
8 changed files with 56 additions and 33 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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]

View File

@@ -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.

View File

@@ -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(

View File

@@ -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(

View File

@@ -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)

View File

@@ -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: