Module textual.layouts.horizontal

Expand source code
from __future__ import annotations

from fractions import Fraction
from typing import cast

from textual.geometry import Size, Region
from textual._layout import ArrangeResult, Layout, WidgetPlacement

from textual.widget import Widget


class HorizontalLayout(Layout):
    """Used to layout Widgets horizontally on screen, from left to right. Since Widgets naturally
    fill the space of their parent container, all widgets used in a horizontal layout should have a specified.
    """

    name = "horizontal"

    def arrange(
        self, parent: Widget, children: list[Widget], size: Size
    ) -> ArrangeResult:

        placements: list[WidgetPlacement] = []
        add_placement = placements.append

        x = max_height = Fraction(0)
        parent_size = parent.outer_size

        styles = [child.styles for child in children if child.styles.width is not None]
        total_fraction = sum(
            [int(style.width.value) for style in styles if style.width.is_fraction]
        )
        fraction_unit = Fraction(size.width, total_fraction or 1)

        box_models = [
            widget._get_box_model(size, parent_size, fraction_unit)
            for widget in cast("list[Widget]", children)
        ]

        margins = [
            max((box1.margin.right, box2.margin.left))
            for box1, box2 in zip(box_models, box_models[1:])
        ]
        if box_models:
            margins.append(box_models[-1].margin.right)

        x = Fraction(box_models[0].margin.left if box_models else 0)

        displayed_children = [child for child in children if child.display]

        _Region = Region
        _WidgetPlacement = WidgetPlacement
        for widget, box_model, margin in zip(children, box_models, margins):
            content_width, content_height, box_margin = box_model
            offset_y = box_margin.top
            next_x = x + content_width
            region = _Region(
                int(x), offset_y, int(next_x - int(x)), int(content_height)
            )
            max_height = max(
                max_height, content_height + offset_y + box_model.margin.bottom
            )
            add_placement(_WidgetPlacement(region, box_model.margin, widget, 0))
            x = next_x + margin

        return placements, set(displayed_children)

    def get_content_width(self, widget: Widget, container: Size, viewport: Size) -> int:
        """Get the width of the content. In Horizontal layout, the content width of
        a widget is the sum of the widths of its children.

        Args:
            widget (Widget): The container widget.
            container (Size): The container size.
            viewport (Size): The viewport size.

        Returns:
            int: Width of the content.
        """
        width: int | None = None
        gutter_width = widget.gutter.width
        for child in widget.displayed_children:
            if not child.is_container:
                child_width = (
                    child.get_content_width(container, viewport)
                    + gutter_width
                    + child.gutter.width
                )
                if width is None:
                    width = child_width
                else:
                    width += child_width
        if width is None:
            width = container.width

        return width

Classes

class HorizontalLayout

Used to layout Widgets horizontally on screen, from left to right. Since Widgets naturally fill the space of their parent container, all widgets used in a horizontal layout should have a specified.

Expand source code
class HorizontalLayout(Layout):
    """Used to layout Widgets horizontally on screen, from left to right. Since Widgets naturally
    fill the space of their parent container, all widgets used in a horizontal layout should have a specified.
    """

    name = "horizontal"

    def arrange(
        self, parent: Widget, children: list[Widget], size: Size
    ) -> ArrangeResult:

        placements: list[WidgetPlacement] = []
        add_placement = placements.append

        x = max_height = Fraction(0)
        parent_size = parent.outer_size

        styles = [child.styles for child in children if child.styles.width is not None]
        total_fraction = sum(
            [int(style.width.value) for style in styles if style.width.is_fraction]
        )
        fraction_unit = Fraction(size.width, total_fraction or 1)

        box_models = [
            widget._get_box_model(size, parent_size, fraction_unit)
            for widget in cast("list[Widget]", children)
        ]

        margins = [
            max((box1.margin.right, box2.margin.left))
            for box1, box2 in zip(box_models, box_models[1:])
        ]
        if box_models:
            margins.append(box_models[-1].margin.right)

        x = Fraction(box_models[0].margin.left if box_models else 0)

        displayed_children = [child for child in children if child.display]

        _Region = Region
        _WidgetPlacement = WidgetPlacement
        for widget, box_model, margin in zip(children, box_models, margins):
            content_width, content_height, box_margin = box_model
            offset_y = box_margin.top
            next_x = x + content_width
            region = _Region(
                int(x), offset_y, int(next_x - int(x)), int(content_height)
            )
            max_height = max(
                max_height, content_height + offset_y + box_model.margin.bottom
            )
            add_placement(_WidgetPlacement(region, box_model.margin, widget, 0))
            x = next_x + margin

        return placements, set(displayed_children)

    def get_content_width(self, widget: Widget, container: Size, viewport: Size) -> int:
        """Get the width of the content. In Horizontal layout, the content width of
        a widget is the sum of the widths of its children.

        Args:
            widget (Widget): The container widget.
            container (Size): The container size.
            viewport (Size): The viewport size.

        Returns:
            int: Width of the content.
        """
        width: int | None = None
        gutter_width = widget.gutter.width
        for child in widget.displayed_children:
            if not child.is_container:
                child_width = (
                    child.get_content_width(container, viewport)
                    + gutter_width
                    + child.gutter.width
                )
                if width is None:
                    width = child_width
                else:
                    width += child_width
        if width is None:
            width = container.width

        return width

Ancestors

  • textual._layout.Layout
  • abc.ABC

Class variables

var name : ClassVar[str]

Methods

def arrange(self, parent: Widget, children: list[Widget], size: Size) ‑> ArrangeResult

Generate a layout map that defines where on the screen the widgets will be drawn.

Args

parent : Widget
Parent widget.
size : Size
Size of container.

Returns

Iterable[WidgetPlacement]
An iterable of widget location
Expand source code
def arrange(
    self, parent: Widget, children: list[Widget], size: Size
) -> ArrangeResult:

    placements: list[WidgetPlacement] = []
    add_placement = placements.append

    x = max_height = Fraction(0)
    parent_size = parent.outer_size

    styles = [child.styles for child in children if child.styles.width is not None]
    total_fraction = sum(
        [int(style.width.value) for style in styles if style.width.is_fraction]
    )
    fraction_unit = Fraction(size.width, total_fraction or 1)

    box_models = [
        widget._get_box_model(size, parent_size, fraction_unit)
        for widget in cast("list[Widget]", children)
    ]

    margins = [
        max((box1.margin.right, box2.margin.left))
        for box1, box2 in zip(box_models, box_models[1:])
    ]
    if box_models:
        margins.append(box_models[-1].margin.right)

    x = Fraction(box_models[0].margin.left if box_models else 0)

    displayed_children = [child for child in children if child.display]

    _Region = Region
    _WidgetPlacement = WidgetPlacement
    for widget, box_model, margin in zip(children, box_models, margins):
        content_width, content_height, box_margin = box_model
        offset_y = box_margin.top
        next_x = x + content_width
        region = _Region(
            int(x), offset_y, int(next_x - int(x)), int(content_height)
        )
        max_height = max(
            max_height, content_height + offset_y + box_model.margin.bottom
        )
        add_placement(_WidgetPlacement(region, box_model.margin, widget, 0))
        x = next_x + margin

    return placements, set(displayed_children)
def get_content_width(self, widget: Widget, container: Size, viewport: Size) ‑> int

Get the width of the content. In Horizontal layout, the content width of a widget is the sum of the widths of its children.

Args

widget : Widget
The container widget.
container : Size
The container size.
viewport : Size
The viewport size.

Returns

int
Width of the content.
Expand source code
def get_content_width(self, widget: Widget, container: Size, viewport: Size) -> int:
    """Get the width of the content. In Horizontal layout, the content width of
    a widget is the sum of the widths of its children.

    Args:
        widget (Widget): The container widget.
        container (Size): The container size.
        viewport (Size): The viewport size.

    Returns:
        int: Width of the content.
    """
    width: int | None = None
    gutter_width = widget.gutter.width
    for child in widget.displayed_children:
        if not child.is_container:
            child_width = (
                child.get_content_width(container, viewport)
                + gutter_width
                + child.gutter.width
            )
            if width is None:
                width = child_width
            else:
                width += child_width
    if width is None:
        width = container.width

    return width