span refinements

This commit is contained in:
Will McGugan
2022-05-10 17:37:27 +01:00
parent f8c7566ecd
commit 8f38ea4494
5 changed files with 48 additions and 53 deletions

View File

@@ -15,7 +15,7 @@ from __future__ import annotations
from operator import attrgetter, itemgetter
import sys
from typing import cast, Iterator, Iterable, NamedTuple, TYPE_CHECKING
from typing import Callable, cast, Iterator, Iterable, NamedTuple, TYPE_CHECKING
import rich.repr
from rich.console import Console, ConsoleOptions, RenderResult, RenderableType
@@ -100,12 +100,15 @@ class SpansUpdate:
self, console: Console, options: ConsoleOptions
) -> RenderResult:
move_to = Control.move_to
for line, offset, segments in self.spans:
yield move_to(offset, line)
new_line = Segment.line()
for last, (y, x, segments) in loop_last(self.spans):
yield move_to(x, y)
yield from segments
if not last:
yield new_line
def __rich_repr__(self) -> rich.repr.Result:
yield [(y, offset, "...") for y, offset, segments in self.spans]
yield [(y, x, "...") for y, x, _segments in self.spans]
@rich.repr.auto(angular=True)
@@ -161,7 +164,7 @@ class Compositor:
ranges.sort()
x1, x2 = ranges[0]
for next_x1, next_x2 in ranges[1:]:
if next_x1 <= x2 + 1:
if next_x1 <= x2:
if next_x2 > x2:
x2 = next_x2
else:
@@ -523,6 +526,7 @@ class Compositor:
is_rendered_line = {y for y, _, _ in spans}.__contains__
else:
crop = screen_region
spans = []
is_rendered_line = lambda y: True
_Segment = Segment
@@ -566,34 +570,25 @@ class Compositor:
if chops_line[cut] is None:
chops_line[cut] = segments
# Assemble the cut renders in to lists of segments
if regions:
crop_x, crop_y, crop_x2, crop_y2 = crop.corners
render_lines = self._assemble_chops(chops[crop_y:crop_y2])
if regions:
render_spans = [
(y, x1, line_crop(render_lines[y], x1, x2)) for y, x1, x2 in spans
(y, x1, line_crop(render_lines[y - crop_y], x1, x2))
for y, x1, x2 in spans
]
return SpansUpdate(render_spans)
else:
return SegmentLines(render_lines, new_lines=True)
if crop is not None and (crop_x, crop_x2) != (0, width):
render_lines = [
line_crop(line, crop_x, crop_x2) if line else line
for line in render_lines
]
return SegmentLines(render_lines, new_lines=True)
render_lines = self._assemble_chops(chops)
return LayoutUpdate(render_lines, screen_region)
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
yield self.render()
def update_widgets(self, *widgets: Widget) -> RenderableType | None:
def update_widgets(self, widgets: set[Widget]) -> RenderableType | None:
"""Update a given widget in the composition.
Args:
@@ -605,21 +600,13 @@ class Compositor:
"""
regions: list[Region] = []
add_region = regions.append
for widget in widgets:
if widget not in self.regions:
continue
for widget in self.regions.keys() & widgets:
region, clip = self.regions[widget]
if not region:
continue
update_region = region.intersection(clip)
if not update_region:
continue
if update_region:
add_region(update_region)
# print(regions)
print("UPDATE_WIDGET")
print(widgets)
print(regions)
update = self.render(regions or None)
# print("UPDATE", update)
return update
# update = LayoutUpdate(update_lines, total_region)
# # print(widgets, total_region)
# return update

View File

@@ -97,8 +97,8 @@ class Screen(Widget):
# Render widgets together
if self._dirty_widgets:
self.log(dirty=len(self._dirty_widgets))
display_update = self._compositor.update_widgets(*self._dirty_widgets)
self.log(dirty=self._dirty_widgets)
display_update = self._compositor.update_widgets(self._dirty_widgets)
if display_update is not None:
self.app.display(display_update)
self._dirty_widgets.clear()
@@ -108,6 +108,9 @@ class Screen(Widget):
"""Refresh the layout (can change size and positions of widgets)."""
if not self.size:
return
# This paint the entire screen, so replaces the batched dirty widgets
self._update_timer.pause()
self._dirty_widgets.clear()
try:
hidden, shown, resized = self._compositor.reflow(self, self.size)
@@ -138,7 +141,6 @@ class Screen(Widget):
self.app.on_exception(error)
return
self.app.refresh()
self._dirty_widgets.clear()
async def handle_update(self, message: messages.Update) -> None:
message.stop()

View File

@@ -227,9 +227,6 @@ class ScrollBar(Widget):
style=style,
)
async def on_event(self, event) -> None:
await super().on_event(event)
async def on_enter(self, event: events.Enter) -> None:
self.mouse_over = True

View File

@@ -272,6 +272,8 @@ class Widget(DOMNode):
self.show_horizontal_scrollbar = show_horizontal
self.show_vertical_scrollbar = show_vertical
self.horizontal_scrollbar.display = show_horizontal
self.vertical_scrollbar.display = show_vertical
@property
def scrollbars_enabled(self) -> tuple[bool, bool]:
@@ -666,6 +668,7 @@ class Widget(DOMNode):
def watch_mouse_over(self, value: bool) -> None:
"""Update from CSS if mouse over state changes."""
return
self.app.update_styles()
def watch_has_focus(self, value: bool) -> None:

View File

@@ -8,41 +8,47 @@ def test_regions_to_ranges_no_regions():
def test_regions_to_ranges_single_region():
regions = [Region(0, 0, 3, 2)]
assert list(Compositor._regions_to_spans(regions)) == [(0, 0, 2), (1, 0, 2)]
assert list(Compositor._regions_to_spans(regions)) == [
(0, 0, 3),
(1, 0, 3),
]
def test_regions_to_ranges_partially_overlapping_regions():
regions = [Region(0, 0, 2, 2), Region(1, 1, 2, 2)]
assert list(Compositor._regions_to_spans(regions)) == [
(0, 0, 1),
(1, 0, 2),
(2, 1, 2),
(0, 0, 2),
(1, 0, 3),
(2, 1, 3),
]
def test_regions_to_ranges_fully_overlapping_regions():
regions = [Region(1, 1, 3, 3), Region(2, 2, 1, 1), Region(0, 2, 3, 1)]
assert list(Compositor._regions_to_spans(regions)) == [
(1, 1, 3),
(2, 0, 3),
(3, 1, 3),
(1, 1, 4),
(2, 0, 4),
(3, 1, 4),
]
def test_regions_to_ranges_disjoint_regions_different_lines():
regions = [Region(0, 0, 2, 1), Region(2, 2, 2, 1)]
assert list(Compositor._regions_to_spans(regions)) == [(0, 0, 1), (2, 2, 3)]
assert list(Compositor._regions_to_spans(regions)) == [(0, 0, 2), (2, 2, 4)]
def test_regions_to_ranges_disjoint_regions_same_line():
regions = [Region(0, 0, 1, 2), Region(2, 0, 1, 1)]
assert list(Compositor._regions_to_spans(regions)) == [
(0, 0, 0),
(0, 2, 2),
(1, 0, 0),
(0, 0, 1),
(0, 2, 3),
(1, 0, 1),
]
def test_regions_to_ranges_directly_adjacent_ranges_merged():
regions = [Region(0, 0, 1, 2), Region(1, 0, 1, 2)]
assert list(Compositor._regions_to_spans(regions)) == [(0, 0, 1), (1, 0, 1)]
assert list(Compositor._regions_to_spans(regions)) == [
(0, 0, 2),
(1, 0, 2),
]