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