virtual size

This commit is contained in:
Will McGugan
2022-03-09 10:29:25 +00:00
parent 5f3720ed0e
commit ea2cf13425
6 changed files with 52 additions and 35 deletions

View File

@@ -10,7 +10,7 @@
/* height: 8; */
margin: 1 2;
text: on dark_blue;
text-background: dark_blue;
}
#child1 {

View File

@@ -8,6 +8,9 @@ from textual.widget import Widget
class BasicApp(App):
"""Sandbox application used for testing/development by Textual developers"""
def on_load(self):
self.bind("q", "quit", "Quit")
def on_mount(self):
"""Build layout here."""
@@ -34,5 +37,8 @@ class BasicApp(App):
async def on_key(self, event: events.Key) -> None:
await self.dispatch_key(event)
def action_quit(self):
self.panic(self.screen.tree)
BasicApp.run(css_file="uber.css", log="textual.log")

View File

@@ -43,6 +43,7 @@ class RenderRegion(NamedTuple):
region: Region
order: tuple[int, ...]
clip: Region
virtual_size: Size
RenderRegionMap: TypeAlias = dict[Widget, RenderRegion]
@@ -133,7 +134,10 @@ class Compositor:
self.width = size.width
self.height = size.height
map, virtual_size, widgets = self._arrange_root(parent)
# TODO: Handle virtual size
map, widgets = self._arrange_root(parent)
log(map)
self._require_update = False
@@ -149,7 +153,7 @@ class Compositor:
# Copy renders if the size hasn't changed
new_renders = {
widget: (region, clip) for widget, (region, _order, clip) in map.items()
widget: (region, clip) for widget, (region, _order, clip, _) in map.items()
}
self.regions = new_renders
@@ -160,14 +164,13 @@ class Compositor:
if widget in old_widgets and widget.size != region.size
}
parent.virtual_size = virtual_size
self.widgets.clear()
self.widgets.update(widgets)
return ReflowResult(
hidden=hidden_widgets, shown=shown_widgets, resized=resized_widgets
)
def _arrange_root(self, root: Widget) -> tuple[RenderRegionMap, Size, set[Widget]]:
def _arrange_root(self, root: Widget) -> tuple[RenderRegionMap, set[Widget]]:
"""Arrange a widgets children based on its layout attribute.
Args:
@@ -177,9 +180,9 @@ class Compositor:
map[dict[Widget, RenderRegion], Size]: A mapping of widget on to render region
and the "virtual size" (scrollable reason)
"""
size = Size(self.width, self.height)
ORIGIN = Offset(0, 0)
ORIGIN = Offset(0, 0)
size = root.size
map: dict[Widget, RenderRegion] = {}
widgets: set[Widget] = set()
@@ -188,7 +191,7 @@ class Compositor:
region: Region,
order: tuple[int, ...],
clip: Region,
):
) -> None:
widgets.add(widget)
styles_offset = widget.styles.offset
total_region = region
@@ -197,7 +200,6 @@ class Compositor:
if styles_offset
else ORIGIN
)
map[widget] = RenderRegion(region + layout_offset, order, clip)
if widget.layout is not None:
scroll = widget.scroll
@@ -220,17 +222,21 @@ class Compositor:
sub_widget.z + (z,),
sub_clip,
)
return total_region.size
virtual_size = add_widget(root, size.region, (), size.region)
return map, virtual_size, widgets
map[widget] = RenderRegion(
region + layout_offset, order, clip, total_region.size
)
add_widget(root, size.region, (), size.region)
return map, widgets
async def mount_all(self, screen: Screen) -> None:
screen.app.mount(*self.widgets)
def __iter__(self) -> Iterator[tuple[Widget, Region, Region]]:
layers = sorted(self.map.items(), key=lambda item: item[1].order, reverse=True)
for widget, (region, order, clip) in layers:
for widget, (region, _order, clip, _) in layers:
yield widget, region.intersection(clip), region
def get_offset(self, widget: Widget) -> Offset:
@@ -312,7 +318,7 @@ class Compositor:
screen_region = Region(0, 0, width, height)
cuts = [[0, width] for _ in range(height)]
for region, order, clip in self.map.values():
for region, order, clip, _ in self.map.values():
region = region.intersection(clip)
if region and (region in screen_region):
region_cuts = (region.x, region.x + region.width)
@@ -337,7 +343,7 @@ class Compositor:
widget_regions = sorted(
[
(widget, region, order, clip)
for widget, (region, order, clip) in self.map.items()
for widget, (region, order, clip, _) in self.map.items()
if widget.is_visual and widget.visible
],
key=itemgetter(2),
@@ -347,12 +353,11 @@ class Compositor:
widget_regions = []
for widget, region, _order, clip in widget_regions:
lines = widget._get_lines()
if region in clip:
lines = widget._get_lines()
yield region, clip, lines
elif clip.overlaps(region):
lines = widget._get_lines()
new_region = region.intersection(clip)
delta_x = new_region.x - region.x
delta_y = new_region.y - region.y

View File

@@ -265,6 +265,7 @@ class DOMNode(MessagePump):
Tree: A Rich object which may be printed.
"""
from rich.columns import Columns
from rich.console import Group
from rich.panel import Panel
highlighter = ReprHighlighter()
@@ -272,21 +273,27 @@ class DOMNode(MessagePump):
def add_children(tree, node):
for child in node.node_list:
cols = [
Pretty(child),
Text(f"{child.size.width} X {child.size.height}", style="dim"),
]
info = Columns(
[
Pretty(child),
highlighter(f"region={child.region!r}"),
highlighter(
f"virtual_size={child.virtual_size!r}",
),
]
)
css = child.styles.css
if css:
cols.append(
Panel(
info = Group(
info,
Panel.fit(
Text(child.styles.css),
border_style="dim",
title="css",
title_align="left",
)
),
branch = tree.add(Columns(cols))
),
)
branch = tree.add(info)
if tree.children:
add_children(branch, child)

View File

@@ -111,11 +111,6 @@ class Size(NamedTuple):
"""A Size is Falsy if it has area 0."""
return self.width * self.height != 0
@property
def clamped(self) -> Size:
width, height = self
return Size(max(0, width), max(0, height))
@property
def area(self) -> int:
"""Get the area of the size.

View File

@@ -29,7 +29,7 @@ from ._callback import invoke
from ._context import active_app
from ._types import Lines
from .dom import DOMNode
from .geometry import Offset, Size
from .geometry import Offset, Region, Size
from .message import Message
from . import messages
from .layout import Layout
@@ -97,7 +97,7 @@ class Widget(DOMNode):
if self.name:
yield "name", self.name
if self.classes:
yield "classes", self.classes
yield "classes", set(self.classes)
pseudo_classes = self.pseudo_classes
if pseudo_classes:
yield "pseudo_classes", pseudo_classes
@@ -159,6 +159,10 @@ class Widget(DOMNode):
def size(self) -> Size:
return self._size
@property
def region(self) -> Region:
return self.screen._compositor.get_widget_region(self)
@property
def scroll(self) -> Offset:
return Offset(self.scroll_x, self.scroll_y)
@@ -265,7 +269,7 @@ class Widget(DOMNode):
# Default displays a pretty repr in the center of the screen
label = f"{self.css_identifier_styled} {self.size}"
label = f"{self.css_identifier_styled} {self.size} {self.virtual_size}"
return Align.center(label, vertical="middle")