mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
simplify arrange
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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__(
|
||||
|
||||
Reference in New Issue
Block a user