This commit is contained in:
Will McGugan
2021-07-27 18:57:37 +01:00
parent 534f7b4dc1
commit 06ebb74242
11 changed files with 120 additions and 78 deletions

View File

@@ -42,7 +42,6 @@ class MyApp(App):
)
self.app.sub_title = os.path.basename(message.path)
await self.body.update(syntax)
# self.body.layout_offset_y = -5
self.body.home()

View File

@@ -331,3 +331,20 @@ class Region(NamedTuple):
_clamp(y2, cy1, cy2),
)
return new_region
def union(self, region: Region) -> Region:
"""Get a new region that contains both regions.
Args:
region (Region): [description]
Returns:
Region: [description]
"""
x1, y1, x2, y2 = self.corners
ox1, oy1, ox2, oy2 = region.corners
union_region = Region.from_corners(
min(x1, ox1), min(y1, oy1), max(x2, ox2), max(y2, oy2)
)
return union_region

View File

@@ -9,11 +9,6 @@ from .geometry import Region, Dimensions
from .widget import Widget
class Order(NamedTuple):
layer: int
z: int
class RenderRegion(NamedTuple):
region: Region
order: tuple[int, ...]
@@ -22,9 +17,13 @@ class RenderRegion(NamedTuple):
class LayoutMap:
def __init__(self, size: Dimensions) -> None:
self.size = size
self.region = size.region
self.widgets: dict[Widget, RenderRegion] = {}
@property
def size(self) -> Dimensions:
return self.region.size
def __getitem__(self, widget: Widget) -> RenderRegion:
return self.widgets[widget]
@@ -52,6 +51,7 @@ class LayoutMap:
region += widget.layout_offset
self.widgets[widget] = RenderRegion(region, order, clip)
self.region = self.region.union(region.intersection(clip))
if isinstance(widget, View):
sub_map = widget.layout.generate_map(console, region.size, region)

View File

@@ -8,9 +8,9 @@ from typing import Iterable, TYPE_CHECKING, Sequence
from rich.console import Console
from .._layout_resolve import layout_resolve
from ..geometry import Region, Point, Dimensions
from ..geometry import Region, Dimensions
from ..layout import Layout
from ..layout_map import LayoutMap, Order
from ..layout_map import LayoutMap
if sys.version_info >= (3, 8):
from typing import Literal

View File

@@ -1,58 +1,60 @@
from __future__ import annotations
from typing import Iterable
from rich.console import Console
from ..geometry import Point, Region, Dimensions
from ..geometry import Region, Dimensions
from ..layout import Layout
from ..layout_map import LayoutMap
from ..widget import Widget
from ..view import View
class VerticalLayout(Layout):
def __init__(self, gutter: tuple[int, int] = (0, 1)):
self.gutter = gutter or (0, 0)
def __init__(self, *, z: int = 0, gutter: tuple[int, int] | None = None):
self.z = z
self.gutter = gutter or (0, 1)
self._widgets: list[Widget] = []
super().__init__()
def add(self, widget: Widget) -> None:
self._widgets.append(widget)
def clear(self) -> None:
del self._widgets[:]
def get_widgets(self) -> Iterable[Widget]:
return self._widgets
def generate_map(
self, console: Console, size: Dimensions, viewport: Region
) -> WidgetMap:
) -> LayoutMap:
offset = viewport.origin
index = 0
width, height = size
gutter_width, gutter_height = self.gutter
render_width = width - gutter_width * 2
x = gutter_width
y = gutter_height
map: dict[Widget, RenderRegion] = {}
map: LayoutMap = LayoutMap(size)
def add_widget(widget: Widget, region: Region):
order = (0, 0)
region = region + widget.layout_offset
map[widget] = RenderRegion(region, order, offset)
if isinstance(widget, View):
sub_map = widget.layout.generate_map(
console,
Dimensions(region.width, region.height),
region.origin + offset,
)
map.update(sub_map)
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:
region_lines = self.renders.get(widget)
if region_lines is None:
try:
region, clip, lines = self.renders[widget]
except KeyError:
renderable = widget.render()
lines = console.render_lines(
renderable, console.options.update_width(render_width)
)
region = Region(x, y, render_width, len(lines))
add_widget(widget, region)
add_widget(widget, region, viewport)
else:
region, lines = region_lines
add_widget(widget, Region(x, y, region.width, region.height))
add_widget(widget, Region(x, y, region.width, region.height), clip)
y += region.height + gutter_height
widget_map = WidgetMap(Dimensions(width, y), map)
return widget_map
return map

View File

@@ -101,24 +101,20 @@ class Page(Widget):
self._page = PageRender(self, renderable, style=style)
super().__init__(name=name)
x: Reactive[int] = Reactive(0)
y: Reactive[int] = Reactive(0)
scroll_x: Reactive[int] = Reactive(0)
scroll_y: Reactive[int] = Reactive(0)
@property
def contents_size(self) -> Dimensions:
return self._page.size
def validate_x(self, value: int) -> int:
def validate_scroll_x(self, value: int) -> int:
return max(0, value)
def validate_y(self, value: int) -> int:
def validate_scroll_y(self, value: int) -> int:
return max(0, value)
async def watch_x(self, new: int) -> None:
async def watch_scroll_x(self, new: int) -> None:
x, y = self._page.offset
self._page.offset = Point(new, y)
async def watch_y(self, new: int) -> None:
async def watch_scroll_y(self, new: int) -> None:
x, y = self._page.offset
self._page.offset = Point(x, new)

View File

@@ -45,40 +45,46 @@ class View(Widget):
super().__init_subclass__(**kwargs)
background: Reactive[str] = Reactive("")
offset_x: Reactive[int] = Reactive(0)
offset_y: Reactive[int] = Reactive(0)
virtual_width: Reactive[int | None] = Reactive(None)
virtual_height: Reactive[int | None] = Reactive(None)
async def watch_background(self, value: str) -> None:
self.layout.background = value
scroll_x: Reactive[int] = Reactive(0)
scroll_y: Reactive[int] = Reactive(0)
@property
def virtual_size(self) -> Dimensions:
virtual_width = self.virtual_width
virtual_height = self.virtual_height
return Dimensions(
(virtual_width if virtual_width is not None else self.size.width),
(virtual_height if virtual_height is not None else self.size.height),
)
return self.layout.map.size
@virtual_size.setter
def virtual_size(self, size: tuple[int, int]) -> None:
width, height = size
self.virtual_width = width
self.virtual_height = height
# virtual_width: Reactive[int | None] = Reactive(None)
# virtual_height: Reactive[int | None] = Reactive(None)
@property
def offset(self) -> Point:
return Point(self.offset_x, self.offset_y)
# @property
# def virtual_size(self) -> Dimensions:
# virtual_width = self.virtual_width
# virtual_height = self.virtual_height
# return Dimensions(
# (virtual_width if virtual_width is not None else self.size.width),
# (virtual_height if virtual_height is not None else self.size.height),
# )
@property
def viewport(self) -> Region:
virtual_width = self.virtual_width
virtual_height = self.virtual_height
width = virtual_width if virtual_width is not None else self.size.width
height = virtual_height if virtual_height is not None else self.size.height
return Region(self.offset_x, self.offset_y, width, height)
# @virtual_size.setter
# def virtual_size(self, size: tuple[int, int]) -> None:
# width, height = size
# self.virtual_width = width
# self.virtual_height = height
# @property
# def offset(self) -> Point:
# return Point(self.offset_x, self.offset_y)
# @property
# def viewport(self) -> Region:
# virtual_width = self.virtual_width
# virtual_height = self.virtual_height
# width = virtual_width if virtual_width is not None else self.size.width
# height = virtual_height if virtual_height is not None else self.size.height
# return Region(self.offset_x, self.offset_y, width, height)
def __rich_console__(
self, console: Console, options: ConsoleOptions
@@ -143,10 +149,10 @@ class View(Widget):
return
width, height = self.console.size
virtual_width, virtual_height = self.virtual_size
viewport = Region(self.offset_x, self.offset_y, width, height)
# virtual_width, virtual_height = self.virtual_size
viewport = Region(self.scroll_x, self.scroll_y, width, height)
hidden, shown, resized = self.layout.reflow(
self.console, virtual_width, virtual_height, viewport
self.console, width, height, viewport
)
self.app.refresh()

View File

@@ -1,2 +1,3 @@
from ._dock_view import DockView, Dock, DockEdge
from ._grid_view import GridView
from ._window_view import WindowView

View File

@@ -0,0 +1,17 @@
from __future__ import annotations
from ..layouts.vertical import VerticalLayout
from ..view import View
from ..widget import Widget
class WindowView(View, layout=VerticalLayout):
def __init__(
self, *, gutter: tuple[int, int] = (1, 1), name: str | None = None
) -> None:
self.gutter = gutter
super().__init__(name=name)
async def update(self, widget: Widget) -> None:
self.layout = VerticalLayout(gutter=self.gutter)
self.layout.add(widget)

View File

@@ -46,23 +46,23 @@ class ScrollView(View):
target_y: Reactive[float] = Reactive(0, repaint=False)
def validate_x(self, value: float) -> float:
return clamp(value, 0, self.page.contents_size.width - self.size.width)
return clamp(value, 0, self.page.virtual_size.width - self.size.width)
def validate_target_x(self, value: float) -> float:
return clamp(value, 0, self.page.contents_size.width - self.size.width)
return clamp(value, 0, self.page.virtual_size.width - self.size.width)
def validate_y(self, value: float) -> float:
return clamp(value, 0, self.page.contents_size.height - self.size.height)
return clamp(value, 0, self.page.virtual_size.height - self.size.height)
def validate_target_y(self, value: float) -> float:
return clamp(value, 0, self.page.contents_size.height - self.size.height)
return clamp(value, 0, self.page.virtual_size.height - self.size.height)
async def watch_x(self, new_value: float) -> None:
self.page.x = round(new_value)
self.page.scroll_x = round(new_value)
self.hscroll.position = round(new_value)
async def watch_y(self, new_value: float) -> None:
self.page.y = round(new_value)
self.page.scroll_y = round(new_value)
self.vscroll.position = round(new_value)
async def update(self, renderabe: RenderableType) -> None:
@@ -132,7 +132,7 @@ class ScrollView(View):
async def key_end(self) -> None:
self.target_x = 0
self.target_y = self.page.contents_size.height - self.size.height
self.target_y = self.page.virtual_size.height - self.size.height
self.animate("x", self.target_x, duration=1, easing="out_cubic")
self.animate("y", self.target_y, duration=1, easing="out_cubic")

View File

@@ -176,3 +176,7 @@ def test_region_intersection():
)
assert not Region(10, 10, 20, 30).intersection(Region(50, 50, 100, 200))
def test_region_union():
assert Region(5, 5, 10, 10).union(Region(20, 30, 10, 5)) == Region(5, 5, 25, 30)