simplify arrange

This commit is contained in:
Will McGugan
2021-09-11 08:51:54 +01:00
parent c970f044d2
commit a0acf61a28
9 changed files with 124 additions and 85 deletions

View File

@@ -22,7 +22,7 @@ class SmoothApp(App):
"""Called when user hits 'b' key."""
self.show_bar = not self.show_bar
async def on_mount(self, event: events.Mount) -> None:
async def on_mount(self) -> None:
"""Build layout here."""
footer = Footer()
self.bar = Placeholder(name="left")

View File

@@ -50,6 +50,14 @@ class ReflowResult(NamedTuple):
resized: set[Widget]
class WidgetPlacement(NamedTuple):
widget: Widget
region: Region
order: tuple[int, ...]
clip: Region
@rich.repr.auto
class LayoutUpdate:
def __init__(self, lines: Lines, region: Region) -> None:
@@ -103,6 +111,13 @@ class Layout(ABC):
def reset(self) -> None:
self._cuts = None
def build_map(self, console: Console, scroll: Offset) -> LayoutMap:
size = Size(console.width, console.height)
layout_map = LayoutMap(size)
for widget, region, order, clip in self.arrange(size, size.region, scroll):
layout_map.add_widget(widget, region, order, clip)
return layout_map
def reflow(
self, console: Console, width: int, height: int, scroll: Offset
) -> ReflowResult:
@@ -111,12 +126,14 @@ class Layout(ABC):
self.width = width
self.height = height
map = self.generate_map(
console,
Size(width, height),
Region(0, 0, width, height),
scroll,
)
# map = self.arrange(
# console,
# Size(width, height),
# Region(0, 0, width, height),
# scroll,
# )
map = self.build_map(console, scroll)
self._require_update = False
old_widgets = set() if self.map is None else set(self.map.keys())
@@ -150,9 +167,9 @@ class Layout(ABC):
...
@abstractmethod
def generate_map(
self, console: Console, size: Size, viewport: Region, scroll: Offset
) -> LayoutMap:
def arrange(
self, size: Size, viewport: Region, scroll: Offset
) -> Iterable[WidgetPlacement]:
"""Generate a layout map that defines where on the screen the widgets will be drawn.
Args:
@@ -161,7 +178,7 @@ class Layout(ABC):
viewport (Region): Screen relative viewport.
Returns:
LayoutMap: [description]
Iterable[WidgetPlacement]: An iterable of widget location
"""
async def mount_all(self, view: "View") -> None:

View File

@@ -4,6 +4,7 @@ from rich.console import Console
from typing import ItemsView, KeysView, ValuesView, NamedTuple
from . import log
from .geometry import Region, Size
from .widget import Widget
@@ -42,7 +43,6 @@ class LayoutMap:
def add_widget(
self,
console: Console,
widget: Widget,
region: Region,
order: tuple[int, ...],
@@ -50,16 +50,25 @@ class LayoutMap:
) -> None:
from .view import View
region += widget.layout_offset
self.widgets[widget] = RenderRegion(region, order, clip)
self.contents_region = self.contents_region.union(region)
if widget in self.widgets:
return
self.widgets[widget] = RenderRegion(region + widget.layout_offset, order, clip)
self.contents_region = self.contents_region.union(region + widget.layout_offset)
if isinstance(widget, View):
sub_map = widget.layout.generate_map(
console, region.size, clip, widget.scroll
widget_placements = list(
widget.layout.arrange(region.size, clip, widget.scroll)
)
widget.virtual_size = sub_map.virtual_size
for sub_widget, (sub_region, sub_order, sub_clip) in sub_map.items():
total_region = Region(0, 0, 0, 0)
for placement in widget_placements:
total_region = total_region.union(placement.region)
widget.virtual_size = total_region.size
log(widget, total_region, widget.virtual_size)
for sub_widget, sub_region, sub_order, sub_clip in widget_placements:
sub_region += region.origin
sub_clip = sub_clip.intersection(clip)
self.add_widget(console, sub_widget, sub_region, sub_order, sub_clip)
# sub_clip = (sub_clip + region.origin).intersection(clip)
# sub_clip = sub_clip + region.origin
self.add_widget(sub_widget, sub_region, sub_order, sub_clip)

View File

@@ -9,7 +9,7 @@ from rich.console import Console
from .._layout_resolve import layout_resolve
from ..geometry import Offset, Region, Size
from ..layout import Layout
from ..layout import Layout, WidgetPlacement
from ..layout_map import LayoutMap
if sys.version_info >= (3, 8):
@@ -48,18 +48,15 @@ class DockLayout(Layout):
for dock in self.docks:
yield from dock.widgets
def generate_map(
self, console: Console, size: Size, viewport: Region, scroll: Offset
) -> LayoutMap:
def arrange(
self, size: Size, viewport: Region, scroll: Offset
) -> Iterable[WidgetPlacement]:
map: LayoutMap = LayoutMap(size)
width, height = size
layout_region = Region(0, 0, width, height)
layers: dict[int, Region] = defaultdict(lambda: layout_region)
def add_widget(widget: Widget, region: Region, order: tuple[int, ...]):
map.add_widget(console, widget, region, order, viewport)
for index, dock in enumerate(self.docks):
dock_options = [
DockOptions(
@@ -87,7 +84,9 @@ class DockLayout(Layout):
if not layout_size:
break
total += layout_size
add_widget(widget, Region(x, render_y, width, layout_size), order)
yield WidgetPlacement(
widget, Region(x, render_y, width, layout_size), order, viewport
)
render_y += layout_size
remaining = max(0, remaining - layout_size)
region = Region(x, y + total, width, height - total) - scroll
@@ -104,10 +103,11 @@ class DockLayout(Layout):
if not layout_size:
break
total += layout_size
add_widget(
yield WidgetPlacement(
widget,
Region(x, render_y - layout_size, width, layout_size),
order,
viewport,
)
render_y -= layout_size
remaining = max(0, remaining - layout_size)
@@ -125,7 +125,12 @@ class DockLayout(Layout):
if not layout_size:
break
total += layout_size
add_widget(widget, Region(render_x, y, layout_size, height), order)
yield WidgetPlacement(
widget,
Region(render_x, y, layout_size, height),
order,
viewport,
)
render_x += layout_size
remaining = max(0, remaining - layout_size)
region = Region(x + total, y, width - total, height) - scroll
@@ -142,10 +147,11 @@ class DockLayout(Layout):
if not layout_size:
break
total += layout_size
add_widget(
yield WidgetPlacement(
widget,
Region(render_x - layout_size, y, layout_size, height),
order,
viewport,
)
render_x -= layout_size
remaining = max(0, remaining - layout_size)

View File

@@ -12,7 +12,7 @@ from rich.console import Console
from .._layout_resolve import layout_resolve
from ..geometry import Size, Offset, Region
from ..layout import Layout
from ..layout import Layout, WidgetPlacement
from ..layout_map import LayoutMap
from ..widget import Widget
@@ -263,9 +263,9 @@ class GridLayout(Layout):
def get_widgets(self) -> Iterable[Widget]:
return self.widgets.keys()
def generate_map(
self, console: Console, size: Size, viewport: Region, scroll: Offset
) -> LayoutMap:
def arrange(
self, size: Size, viewport: Region, scroll: Offset
) -> Iterable[WidgetPlacement]:
"""Generate a map that associates widgets with their location on screen.
Args:
@@ -276,7 +276,6 @@ class GridLayout(Layout):
Returns:
dict[Widget, OrderedRegion]: [description]
"""
map: LayoutMap = LayoutMap(size)
width, height = size
def resolve(
@@ -327,17 +326,6 @@ class GridLayout(Layout):
return names, tracks, len(spans), max_size
def add_widget(widget: Widget, region: Region, order: tuple[int, int]):
region -= scroll
map.add_widget(console, widget, region, order, viewport)
# region = region + widget.layout_offset
# map[widget] = RenderRegion(region, order, offset)
# if isinstance(widget, View):
# sub_map = widget.layout.generate_map(
# region.width, region.height, region.origin + offset
# )
# map.update(sub_map)
container = Size(width - self.column_gutter * 2, height - self.row_gutter * 2)
column_names, column_tracks, column_count, column_size = resolve_tracks(
[
@@ -390,7 +378,9 @@ class GridLayout(Layout):
self.column_align,
self.row_align,
)
add_widget(widget, region + gutter, (0, order))
yield WidgetPlacement(
widget, region + gutter - scroll, (0, order), viewport
)
order += 1
# Widgets with no area assigned.
@@ -422,7 +412,9 @@ class GridLayout(Layout):
self.column_align,
self.row_align,
)
add_widget(widget, region + gutter, (0, order))
yield WidgetPlacement(
widget, region + gutter - scroll, (0, order), viewport
)
order += 1
return map

View File

@@ -7,7 +7,7 @@ from rich.measure import Measurement
from .. import log
from ..geometry import Offset, Region, Size
from ..layout import Layout
from ..layout import Layout, WidgetPlacement
from ..layout_map import LayoutMap
from ..widget import Widget
@@ -38,9 +38,9 @@ class VerticalLayout(Layout):
def get_widgets(self) -> Iterable[Widget]:
return self._widgets
def generate_map(
self, console: Console, size: Size, viewport: Region, scroll: Offset
) -> LayoutMap:
def arrange(
self, size: Size, viewport: Region, scroll: Offset
) -> Iterable[WidgetPlacement]:
index = 0
width, height = size
gutter_height, gutter_width = self.gutter
@@ -52,10 +52,6 @@ class VerticalLayout(Layout):
x = gutter_width
y = gutter_height
map: LayoutMap = LayoutMap(size)
def add_widget(widget: Widget, region: Region, clip: Region) -> None:
map.add_widget(console, widget, region, (self.z, index), clip)
for widget in self._widgets:
if (
@@ -66,11 +62,11 @@ class VerticalLayout(Layout):
assert widget.render_cache is not None
render_height = widget.render_cache.size.height
region = Region(x, y, render_width, render_height)
add_widget(widget, region - scroll, viewport)
yield WidgetPlacement(widget, region - scroll, (self.z, index), viewport)
x, y, width, height = map.contents_region
map.contents_region = Region(
x, y, width + self.gutter[0], height + self.gutter[1]
)
# x, y, width, height = map.contents_region
# map.contents_region = Region(
# x, y, width + self.gutter[0], height + self.gutter[1]
# )
return map

View File

@@ -118,34 +118,42 @@ class View(Widget):
self.refresh()
async def refresh_layout(self) -> None:
await self.layout.mount_all(self)
if not self.is_root_view:
await self.app.view.refresh_layout()
return
try:
await self.layout.mount_all(self)
if not self.is_root_view:
await self.app.view.refresh_layout()
return
if not self.size:
return
if not self.size:
return
width, height = self.console.size
hidden, shown, resized = self.layout.reflow(
self.console, width, height, self.scroll
)
# if self.layout._layout_map is not None:
# self.log(self.layout._layout_map.widgets)
assert self.layout.map is not None
self.virtual_size = self.layout.map.virtual_size
width, height = self.console.size
hidden, shown, resized = self.layout.reflow(
self.console, width, height, self.scroll
)
for widget in hidden:
widget.post_message_no_wait(events.Hide(self))
for widget in shown:
widget.post_message_no_wait(events.Show(self))
assert self.layout.map is not None
self.virtual_size = self.layout.map.virtual_size
send_resize = shown
send_resize.update(resized)
for widget in hidden:
widget.post_message_no_wait(events.Hide(self))
for widget in shown:
widget.post_message_no_wait(events.Show(self))
for widget, region, unclipped_region in self.layout:
widget._update_size(unclipped_region.size)
if widget in send_resize:
widget.post_message_no_wait(events.Resize(self, unclipped_region.size))
send_resize = shown
send_resize.update(resized)
for widget, region, unclipped_region in self.layout:
widget._update_size(unclipped_region.size)
if widget in send_resize:
widget.post_message_no_wait(
events.Resize(self, unclipped_region.size)
)
except:
self.app.panic()
async def on_resize(self, event: events.Resize) -> None:
self._update_size(event.size)

View File

@@ -10,6 +10,7 @@ import rich.repr
from logging import getLogger
from .. import events
from ..geometry import Offset
from ..widget import Reactive, Widget
log = getLogger("rich")

View File

@@ -1,9 +1,9 @@
from __future__ import annotations
from functools import lru_cache
from typing import Generic, Iterator, NewType, TypeVar
import rich.repr
from rich.console import RenderableType
from rich.text import Text, TextType
from rich.tree import Tree
@@ -24,6 +24,7 @@ NodeID = NewType("NodeID", int)
NodeDataType = TypeVar("NodeDataType")
@rich.repr.auto
class TreeNode(Generic[NodeDataType]):
def __init__(
self,
@@ -46,6 +47,11 @@ class TreeNode(Generic[NodeDataType]):
self._tree.expanded = False
self.children: list[TreeNode] = []
def __rich_repr__(self) -> rich.repr.Result:
yield "id", self.id
yield "label", self.label
yield "data", self.data
@property
def control(self) -> TreeControl:
return self._control
@@ -154,11 +160,15 @@ class TreeNode(Generic[NodeDataType]):
return self._control.render_node(self)
@rich.repr.auto
class TreeClick(Generic[NodeDataType], Message, bubble=True):
def __init__(self, sender: MessageTarget, node: TreeNode[NodeDataType]) -> None:
self.node = node
super().__init__(sender)
def __rich_repr__(self) -> rich.repr.Result:
yield "node", self.node
class TreeControl(Generic[NodeDataType], Widget):
def __init__(