refactor layout

This commit is contained in:
Will McGugan
2021-09-11 14:44:35 +01:00
parent 12f7a09b76
commit 945601281d
10 changed files with 61 additions and 100 deletions

View File

@@ -3,11 +3,11 @@ from typing import Any
__all__ = ["log", "panic"]
def log(*args: Any, verbosity: int = 0) -> None:
def log(*args: Any, verbosity: int = 0, **kwargs) -> None:
from ._context import active_app
app = active_app.get()
app.log(*args, verbosity=verbosity)
app.log(*args, verbosity=verbosity, **kwargs)
def panic(*args: Any) -> None:

View File

@@ -124,7 +124,7 @@ class App(MessagePump):
def view(self) -> DockView:
return self._view_stack[-1]
def log(self, *args: Any, verbosity: int = 1) -> None:
def log(self, *args: Any, verbosity: int = 1, **kwargs) -> None:
"""Write to logs.
Args:
@@ -134,6 +134,10 @@ class App(MessagePump):
try:
if self.log_file and verbosity <= self.log_verbosity:
output = f" ".join(str(arg) for arg in args)
if kwargs:
output += " " + " ".join(
f"{key}={value}" for key, value in kwargs.items()
)
self.log_file.write(output + "\n")
self.log_file.flush()
except Exception:
@@ -452,6 +456,7 @@ class App(MessagePump):
async def broker_event(
self, event_name: str, event: events.Event, default_namespace: object | None
) -> bool:
event.stop()
try:
style = getattr(event, "style")
except AttributeError:
@@ -471,7 +476,6 @@ class App(MessagePump):
return True
async def on_key(self, event: events.Key) -> None:
# self.log("App.on_key")
await self.press(event.key)
async def on_shutdown_request(self, event: events.ShutdownRequest) -> None:

View File

@@ -52,10 +52,9 @@ class ReflowResult(NamedTuple):
class WidgetPlacement(NamedTuple):
widget: Widget
region: Region
order: tuple[int, ...]
clip: Region
widget: Widget | None = None
order: tuple[int, ...] = ()
@rich.repr.auto
@@ -111,29 +110,15 @@ 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:
def reflow(self, view: View, size: Size) -> ReflowResult:
self.reset()
self.width = width
self.height = height
self.width = size.width
self.height = size.height
# map = self.arrange(
# console,
# Size(width, height),
# Region(0, 0, width, height),
# scroll,
# )
map = LayoutMap(size)
map.add_widget(view, size.region, (), size.region)
map = self.build_map(console, scroll)
self._require_update = False
old_widgets = set() if self.map is None else set(self.map.keys())
@@ -167,9 +152,7 @@ class Layout(ABC):
...
@abstractmethod
def arrange(
self, size: Size, viewport: Region, scroll: Offset
) -> Iterable[WidgetPlacement]:
def arrange(self, size: Size, scroll: Offset) -> Iterable[WidgetPlacement]:
"""Generate a layout map that defines where on the screen the widgets will be drawn.
Args:
@@ -403,6 +386,4 @@ class Layout(ABC):
update_region = region.intersection(clip)
update_lines = self.render(console, crop=update_region).lines
update = LayoutUpdate(update_lines, update_region)
log(update)
return update

View File

@@ -19,13 +19,8 @@ class RenderRegion(NamedTuple):
class LayoutMap:
def __init__(self, size: Size) -> None:
self.size = size
self.contents_region = Region(0, 0, 0, 0)
self.widgets: dict[Widget, RenderRegion] = {}
@property
def virtual_size(self) -> Size:
return self.contents_region.size
def __getitem__(self, widget: Widget) -> RenderRegion:
return self.widgets[widget]
@@ -54,21 +49,20 @@ class LayoutMap:
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):
widget_placements = list(
widget.layout.arrange(region.size, clip, widget.scroll)
)
total_region = Region(0, 0, 0, 0)
for placement in widget_placements:
total_region = total_region.union(placement.region)
scroll = widget.scroll
total_region = region.size.region
sub_clip = clip.intersection(region)
arrangement = widget.layout.arrange(region.size, scroll)
for sub_region, sub_widget, sub_order in arrangement:
total_region = total_region.union(sub_region)
if sub_widget is not None:
self.add_widget(
sub_widget,
sub_region + region.origin - scroll,
sub_order,
sub_clip,
)
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)
# 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

@@ -48,9 +48,7 @@ class DockLayout(Layout):
for dock in self.docks:
yield from dock.widgets
def arrange(
self, size: Size, viewport: Region, scroll: Offset
) -> Iterable[WidgetPlacement]:
def arrange(self, size: Size, scroll: Offset) -> Iterable[WidgetPlacement]:
map: LayoutMap = LayoutMap(size)
width, height = size
@@ -85,11 +83,11 @@ class DockLayout(Layout):
break
total += layout_size
yield WidgetPlacement(
widget, Region(x, render_y, width, layout_size), order, viewport
Region(x, render_y, width, layout_size), widget, order
)
render_y += layout_size
remaining = max(0, remaining - layout_size)
region = Region(x, y + total, width, height - total) - scroll
region = Region(x, y + total, width, height - total)
elif dock.edge == "bottom":
sizes = layout_resolve(height, dock_options)
@@ -104,14 +102,13 @@ class DockLayout(Layout):
break
total += layout_size
yield WidgetPlacement(
widget,
Region(x, render_y - layout_size, width, layout_size),
widget,
order,
viewport,
)
render_y -= layout_size
remaining = max(0, remaining - layout_size)
region = Region(x, y, width, height - total) - scroll
region = Region(x, y, width, height - total)
elif dock.edge == "left":
sizes = layout_resolve(width, dock_options)
@@ -126,14 +123,13 @@ class DockLayout(Layout):
break
total += layout_size
yield WidgetPlacement(
widget,
Region(render_x, y, layout_size, height),
widget,
order,
viewport,
)
render_x += layout_size
remaining = max(0, remaining - layout_size)
region = Region(x + total, y, width - total, height) - scroll
region = Region(x + total, y, width - total, height)
elif dock.edge == "right":
sizes = layout_resolve(width, dock_options)
@@ -148,14 +144,13 @@ class DockLayout(Layout):
break
total += layout_size
yield WidgetPlacement(
widget,
Region(render_x - layout_size, y, layout_size, height),
widget,
order,
viewport,
)
render_x -= layout_size
remaining = max(0, remaining - layout_size)
region = Region(x, y, width - total, height) - scroll
region = Region(x, y, width - total, height)
layers[dock.z] = region

View File

@@ -263,9 +263,7 @@ class GridLayout(Layout):
def get_widgets(self) -> Iterable[Widget]:
return self.widgets.keys()
def arrange(
self, size: Size, viewport: Region, scroll: Offset
) -> Iterable[WidgetPlacement]:
def arrange(self, size: Size, scroll: Offset) -> Iterable[WidgetPlacement]:
"""Generate a map that associates widgets with their location on screen.
Args:
@@ -378,9 +376,7 @@ class GridLayout(Layout):
self.column_align,
self.row_align,
)
yield WidgetPlacement(
widget, region + gutter - scroll, (0, order), viewport
)
yield WidgetPlacement(region + gutter, widget, (0, order))
order += 1
# Widgets with no area assigned.
@@ -412,9 +408,7 @@ class GridLayout(Layout):
self.column_align,
self.row_align,
)
yield WidgetPlacement(
widget, region + gutter - scroll, (0, order), viewport
)
yield WidgetPlacement(region + gutter, widget, (0, order))
order += 1
return map

View File

@@ -38,9 +38,7 @@ class VerticalLayout(Layout):
def get_widgets(self) -> Iterable[Widget]:
return self._widgets
def arrange(
self, size: Size, viewport: Region, scroll: Offset
) -> Iterable[WidgetPlacement]:
def arrange(self, size: Size, scroll: Offset) -> Iterable[WidgetPlacement]:
index = 0
width, height = size
gutter_height, gutter_width = self.gutter
@@ -53,6 +51,8 @@ class VerticalLayout(Layout):
x = gutter_width
y = gutter_height
total_region = size.region
for widget in self._widgets:
if (
not widget.render_cache
@@ -62,11 +62,13 @@ 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)
yield WidgetPlacement(widget, region - scroll, (self.z, index), viewport)
yield WidgetPlacement(region, widget, (self.z, index))
total_region = total_region.union(region)
yield WidgetPlacement(total_region)
# x, y, width, height = map.contents_region
# map.contents_region = Region(
# x, y, width + self.gutter[0], height + self.gutter[1]
# )
return map
# return map

View File

@@ -65,8 +65,8 @@ class MessagePump:
def is_running(self) -> bool:
return self._running
def log(self, *args) -> None:
return self.app.log(*args)
def log(self, *args, **kwargs) -> None:
return self.app.log(*args, **kwargs)
def set_parent(self, parent: MessagePump) -> None:
self._parent = parent

View File

@@ -43,20 +43,18 @@ class View(Widget):
super().__init_subclass__(**kwargs)
background: Reactive[str] = Reactive("")
scroll_x: Reactive[int] = Reactive(0)
scroll_y: Reactive[int] = Reactive(0)
virtual_size = Reactive(Size(0, 0))
async def watch_background(self, value: str) -> None:
self.layout.background = value
self.app.refresh()
scroll_x: Reactive[int] = Reactive(0)
scroll_y: Reactive[int] = Reactive(0)
@property
def scroll(self) -> Offset:
return Offset(self.scroll_x, self.scroll_y)
virtual_size: Reactive[Size] = Reactive(Size(0, 0))
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
@@ -127,16 +125,10 @@ class View(Widget):
if not self.size:
return
# if self.layout._layout_map is not None:
# self.log(self.layout._layout_map.widgets)
width, height = self.console.size
hidden, shown, resized = self.layout.reflow(
self.console, width, height, self.scroll
)
hidden, shown, resized = self.layout.reflow(self, Size(*self.console.size))
assert self.layout.map is not None
self.virtual_size = self.layout.map.virtual_size
# self.virtual_size = self.layout.map.virtual_size
for widget in hidden:
widget.post_message_no_wait(events.Hide(self))

View File

@@ -173,16 +173,16 @@ class ScrollView(View):
self.animate("x", self.target_x, duration=1, easing="out_cubic")
self.animate("y", self.target_y, duration=1, easing="out_cubic")
async def handle_scroll_up(self, message: Message) -> None:
async def handle_scroll_up(self) -> None:
self.page_up()
async def handle_scroll_down(self, message: Message) -> None:
async def handle_scroll_down(self) -> None:
self.page_down()
async def handle_scroll_left(self, message: Message) -> None:
async def handle_scroll_left(self) -> None:
self.page_left()
async def handle_scroll_right(self, message: Message) -> None:
async def handle_scroll_right(self) -> None:
self.page_right()
async def handle_scroll_to(self, message: ScrollTo) -> None:
@@ -193,11 +193,10 @@ class ScrollView(View):
self.animate("x", self.target_x, speed=150, easing="out_cubic")
self.animate("y", self.target_y, speed=150, easing="out_cubic")
def handle_window_change(self) -> None:
virtual_width, virtual_height = self.virtual_size
def handle_window_change(self, message) -> None:
virtual_width, virtual_height = self.window.virtual_size
width, height = self.size
self.log(self.virtual_size, self.size)
self.x = self.validate_x(self.x)
self.y = self.validate_y(self.y)