125 lines
3.8 KiB
Python
125 lines
3.8 KiB
Python
from __future__ import annotations
|
|
|
|
from fractions import Fraction
|
|
from typing import TYPE_CHECKING
|
|
|
|
from textual._resolve import resolve_box_models
|
|
from textual.geometry import NULL_OFFSET, Region, Size
|
|
from textual.layout import ArrangeResult, Layout, WidgetPlacement
|
|
|
|
if TYPE_CHECKING:
|
|
from textual.geometry import Spacing
|
|
from textual.widget import Widget
|
|
|
|
|
|
class VerticalLayout(Layout):
|
|
"""Used to layout Widgets vertically on screen, from top to bottom."""
|
|
|
|
name = "vertical"
|
|
|
|
def arrange(
|
|
self, parent: Widget, children: list[Widget], size: Size, greedy: bool = True
|
|
) -> ArrangeResult:
|
|
parent.pre_layout(self)
|
|
placements: list[WidgetPlacement] = []
|
|
add_placement = placements.append
|
|
viewport = parent.app.viewport_size
|
|
|
|
child_styles = [child.styles for child in children]
|
|
box_margins: list[Spacing] = [
|
|
styles.margin for styles in child_styles if styles.overlay != "screen"
|
|
]
|
|
if box_margins:
|
|
resolve_margin = Size(
|
|
max(
|
|
[
|
|
margin_right + margin_left
|
|
for _, margin_right, _, margin_left in box_margins
|
|
]
|
|
),
|
|
sum(
|
|
[
|
|
bottom if bottom > top else top
|
|
for (_, _, bottom, _), (top, _, _, _) in zip(
|
|
box_margins, box_margins[1:]
|
|
)
|
|
]
|
|
)
|
|
+ (box_margins[0].top + box_margins[-1].bottom),
|
|
)
|
|
else:
|
|
resolve_margin = Size(0, 0)
|
|
|
|
box_models = resolve_box_models(
|
|
[styles.height for styles in child_styles],
|
|
children,
|
|
size,
|
|
parent.app.size,
|
|
resolve_margin,
|
|
resolve_dimension="height",
|
|
greedy=greedy,
|
|
)
|
|
|
|
margins = [
|
|
(
|
|
margin_bottom
|
|
if (margin_bottom := margin1.bottom) > (margin_top := margin2.top)
|
|
else margin_top
|
|
)
|
|
for (_, _, margin1), (_, _, margin2) in zip(box_models, box_models[1:])
|
|
]
|
|
|
|
if box_models:
|
|
margins.append(box_models[-1].margin.bottom)
|
|
|
|
y = next(
|
|
(
|
|
Fraction(box_model.margin.top)
|
|
for box_model, child in zip(box_models, children)
|
|
if child.styles.overlay != "screen"
|
|
),
|
|
Fraction(0),
|
|
)
|
|
|
|
_Region = Region
|
|
_WidgetPlacement = WidgetPlacement
|
|
_Size = Size
|
|
for widget, (content_width, content_height, box_margin), margin in zip(
|
|
children, box_models, margins
|
|
):
|
|
styles = widget.styles
|
|
overlay = styles.overlay == "screen"
|
|
next_y = y + content_height
|
|
offset = (
|
|
styles.offset.resolve(
|
|
_Size(content_width.__floor__(), content_height.__floor__()),
|
|
viewport,
|
|
)
|
|
if styles.has_rule("offset")
|
|
else NULL_OFFSET
|
|
)
|
|
|
|
region = _Region(
|
|
box_margin.left,
|
|
y.__floor__(),
|
|
content_width.__floor__(),
|
|
next_y.__floor__() - y.__floor__(),
|
|
)
|
|
absolute = styles.has_rule("position") and styles.position == "absolute"
|
|
add_placement(
|
|
_WidgetPlacement(
|
|
region,
|
|
offset,
|
|
box_margin,
|
|
widget,
|
|
0,
|
|
False,
|
|
overlay,
|
|
absolute,
|
|
)
|
|
)
|
|
if not overlay and not absolute:
|
|
y = next_y + margin
|
|
|
|
return placements
|