diff options
| author | cyfraeviolae <cyfraeviolae> | 2024-04-03 03:10:44 -0400 | 
|---|---|---|
| committer | cyfraeviolae <cyfraeviolae> | 2024-04-03 03:10:44 -0400 | 
| commit | 6d7ba58f880be618ade07f8ea080fe8c4bf8a896 (patch) | |
| tree | b1c931051ffcebd2bd9d61d98d6233ffa289bbce /venv/lib/python3.11/site-packages/rich/layout.py | |
| parent | 4f884c9abc32990b4061a1bb6997b4b37e58ea0b (diff) | |
venv
Diffstat (limited to 'venv/lib/python3.11/site-packages/rich/layout.py')
| -rw-r--r-- | venv/lib/python3.11/site-packages/rich/layout.py | 442 | 
1 files changed, 442 insertions, 0 deletions
| diff --git a/venv/lib/python3.11/site-packages/rich/layout.py b/venv/lib/python3.11/site-packages/rich/layout.py new file mode 100644 index 0000000..7fa2852 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich/layout.py @@ -0,0 +1,442 @@ +from abc import ABC, abstractmethod +from itertools import islice +from operator import itemgetter +from threading import RLock +from typing import ( +    TYPE_CHECKING, +    Dict, +    Iterable, +    List, +    NamedTuple, +    Optional, +    Sequence, +    Tuple, +    Union, +) + +from ._ratio import ratio_resolve +from .align import Align +from .console import Console, ConsoleOptions, RenderableType, RenderResult +from .highlighter import ReprHighlighter +from .panel import Panel +from .pretty import Pretty +from .region import Region +from .repr import Result, rich_repr +from .segment import Segment +from .style import StyleType + +if TYPE_CHECKING: +    from rich.tree import Tree + + +class LayoutRender(NamedTuple): +    """An individual layout render.""" + +    region: Region +    render: List[List[Segment]] + + +RegionMap = Dict["Layout", Region] +RenderMap = Dict["Layout", LayoutRender] + + +class LayoutError(Exception): +    """Layout related error.""" + + +class NoSplitter(LayoutError): +    """Requested splitter does not exist.""" + + +class _Placeholder: +    """An internal renderable used as a Layout placeholder.""" + +    highlighter = ReprHighlighter() + +    def __init__(self, layout: "Layout", style: StyleType = "") -> None: +        self.layout = layout +        self.style = style + +    def __rich_console__( +        self, console: Console, options: ConsoleOptions +    ) -> RenderResult: +        width = options.max_width +        height = options.height or options.size.height +        layout = self.layout +        title = ( +            f"{layout.name!r} ({width} x {height})" +            if layout.name +            else f"({width} x {height})" +        ) +        yield Panel( +            Align.center(Pretty(layout), vertical="middle"), +            style=self.style, +            title=self.highlighter(title), +            border_style="blue", +            height=height, +        ) + + +class Splitter(ABC): +    """Base class for a splitter.""" + +    name: str = "" + +    @abstractmethod +    def get_tree_icon(self) -> str: +        """Get the icon (emoji) used in layout.tree""" + +    @abstractmethod +    def divide( +        self, children: Sequence["Layout"], region: Region +    ) -> Iterable[Tuple["Layout", Region]]: +        """Divide a region amongst several child layouts. + +        Args: +            children (Sequence(Layout)): A number of child layouts. +            region (Region): A rectangular region to divide. +        """ + + +class RowSplitter(Splitter): +    """Split a layout region in to rows.""" + +    name = "row" + +    def get_tree_icon(self) -> str: +        return "[layout.tree.row]⬌" + +    def divide( +        self, children: Sequence["Layout"], region: Region +    ) -> Iterable[Tuple["Layout", Region]]: +        x, y, width, height = region +        render_widths = ratio_resolve(width, children) +        offset = 0 +        _Region = Region +        for child, child_width in zip(children, render_widths): +            yield child, _Region(x + offset, y, child_width, height) +            offset += child_width + + +class ColumnSplitter(Splitter): +    """Split a layout region in to columns.""" + +    name = "column" + +    def get_tree_icon(self) -> str: +        return "[layout.tree.column]⬍" + +    def divide( +        self, children: Sequence["Layout"], region: Region +    ) -> Iterable[Tuple["Layout", Region]]: +        x, y, width, height = region +        render_heights = ratio_resolve(height, children) +        offset = 0 +        _Region = Region +        for child, child_height in zip(children, render_heights): +            yield child, _Region(x, y + offset, width, child_height) +            offset += child_height + + +@rich_repr +class Layout: +    """A renderable to divide a fixed height in to rows or columns. + +    Args: +        renderable (RenderableType, optional): Renderable content, or None for placeholder. Defaults to None. +        name (str, optional): Optional identifier for Layout. Defaults to None. +        size (int, optional): Optional fixed size of layout. Defaults to None. +        minimum_size (int, optional): Minimum size of layout. Defaults to 1. +        ratio (int, optional): Optional ratio for flexible layout. Defaults to 1. +        visible (bool, optional): Visibility of layout. Defaults to True. +    """ + +    splitters = {"row": RowSplitter, "column": ColumnSplitter} + +    def __init__( +        self, +        renderable: Optional[RenderableType] = None, +        *, +        name: Optional[str] = None, +        size: Optional[int] = None, +        minimum_size: int = 1, +        ratio: int = 1, +        visible: bool = True, +    ) -> None: +        self._renderable = renderable or _Placeholder(self) +        self.size = size +        self.minimum_size = minimum_size +        self.ratio = ratio +        self.name = name +        self.visible = visible +        self.splitter: Splitter = self.splitters["column"]() +        self._children: List[Layout] = [] +        self._render_map: RenderMap = {} +        self._lock = RLock() + +    def __rich_repr__(self) -> Result: +        yield "name", self.name, None +        yield "size", self.size, None +        yield "minimum_size", self.minimum_size, 1 +        yield "ratio", self.ratio, 1 + +    @property +    def renderable(self) -> RenderableType: +        """Layout renderable.""" +        return self if self._children else self._renderable + +    @property +    def children(self) -> List["Layout"]: +        """Gets (visible) layout children.""" +        return [child for child in self._children if child.visible] + +    @property +    def map(self) -> RenderMap: +        """Get a map of the last render.""" +        return self._render_map + +    def get(self, name: str) -> Optional["Layout"]: +        """Get a named layout, or None if it doesn't exist. + +        Args: +            name (str): Name of layout. + +        Returns: +            Optional[Layout]: Layout instance or None if no layout was found. +        """ +        if self.name == name: +            return self +        else: +            for child in self._children: +                named_layout = child.get(name) +                if named_layout is not None: +                    return named_layout +        return None + +    def __getitem__(self, name: str) -> "Layout": +        layout = self.get(name) +        if layout is None: +            raise KeyError(f"No layout with name {name!r}") +        return layout + +    @property +    def tree(self) -> "Tree": +        """Get a tree renderable to show layout structure.""" +        from rich.styled import Styled +        from rich.table import Table +        from rich.tree import Tree + +        def summary(layout: "Layout") -> Table: +            icon = layout.splitter.get_tree_icon() + +            table = Table.grid(padding=(0, 1, 0, 0)) + +            text: RenderableType = ( +                Pretty(layout) if layout.visible else Styled(Pretty(layout), "dim") +            ) +            table.add_row(icon, text) +            _summary = table +            return _summary + +        layout = self +        tree = Tree( +            summary(layout), +            guide_style=f"layout.tree.{layout.splitter.name}", +            highlight=True, +        ) + +        def recurse(tree: "Tree", layout: "Layout") -> None: +            for child in layout._children: +                recurse( +                    tree.add( +                        summary(child), +                        guide_style=f"layout.tree.{child.splitter.name}", +                    ), +                    child, +                ) + +        recurse(tree, self) +        return tree + +    def split( +        self, +        *layouts: Union["Layout", RenderableType], +        splitter: Union[Splitter, str] = "column", +    ) -> None: +        """Split the layout in to multiple sub-layouts. + +        Args: +            *layouts (Layout): Positional arguments should be (sub) Layout instances. +            splitter (Union[Splitter, str]): Splitter instance or name of splitter. +        """ +        _layouts = [ +            layout if isinstance(layout, Layout) else Layout(layout) +            for layout in layouts +        ] +        try: +            self.splitter = ( +                splitter +                if isinstance(splitter, Splitter) +                else self.splitters[splitter]() +            ) +        except KeyError: +            raise NoSplitter(f"No splitter called {splitter!r}") +        self._children[:] = _layouts + +    def add_split(self, *layouts: Union["Layout", RenderableType]) -> None: +        """Add a new layout(s) to existing split. + +        Args: +            *layouts (Union[Layout, RenderableType]): Positional arguments should be renderables or (sub) Layout instances. + +        """ +        _layouts = ( +            layout if isinstance(layout, Layout) else Layout(layout) +            for layout in layouts +        ) +        self._children.extend(_layouts) + +    def split_row(self, *layouts: Union["Layout", RenderableType]) -> None: +        """Split the layout in to a row (layouts side by side). + +        Args: +            *layouts (Layout): Positional arguments should be (sub) Layout instances. +        """ +        self.split(*layouts, splitter="row") + +    def split_column(self, *layouts: Union["Layout", RenderableType]) -> None: +        """Split the layout in to a column (layouts stacked on top of each other). + +        Args: +            *layouts (Layout): Positional arguments should be (sub) Layout instances. +        """ +        self.split(*layouts, splitter="column") + +    def unsplit(self) -> None: +        """Reset splits to initial state.""" +        del self._children[:] + +    def update(self, renderable: RenderableType) -> None: +        """Update renderable. + +        Args: +            renderable (RenderableType): New renderable object. +        """ +        with self._lock: +            self._renderable = renderable + +    def refresh_screen(self, console: "Console", layout_name: str) -> None: +        """Refresh a sub-layout. + +        Args: +            console (Console): Console instance where Layout is to be rendered. +            layout_name (str): Name of layout. +        """ +        with self._lock: +            layout = self[layout_name] +            region, _lines = self._render_map[layout] +            (x, y, width, height) = region +            lines = console.render_lines( +                layout, console.options.update_dimensions(width, height) +            ) +            self._render_map[layout] = LayoutRender(region, lines) +            console.update_screen_lines(lines, x, y) + +    def _make_region_map(self, width: int, height: int) -> RegionMap: +        """Create a dict that maps layout on to Region.""" +        stack: List[Tuple[Layout, Region]] = [(self, Region(0, 0, width, height))] +        push = stack.append +        pop = stack.pop +        layout_regions: List[Tuple[Layout, Region]] = [] +        append_layout_region = layout_regions.append +        while stack: +            append_layout_region(pop()) +            layout, region = layout_regions[-1] +            children = layout.children +            if children: +                for child_and_region in layout.splitter.divide(children, region): +                    push(child_and_region) + +        region_map = { +            layout: region +            for layout, region in sorted(layout_regions, key=itemgetter(1)) +        } +        return region_map + +    def render(self, console: Console, options: ConsoleOptions) -> RenderMap: +        """Render the sub_layouts. + +        Args: +            console (Console): Console instance. +            options (ConsoleOptions): Console options. + +        Returns: +            RenderMap: A dict that maps Layout on to a tuple of Region, lines +        """ +        render_width = options.max_width +        render_height = options.height or console.height +        region_map = self._make_region_map(render_width, render_height) +        layout_regions = [ +            (layout, region) +            for layout, region in region_map.items() +            if not layout.children +        ] +        render_map: Dict["Layout", "LayoutRender"] = {} +        render_lines = console.render_lines +        update_dimensions = options.update_dimensions + +        for layout, region in layout_regions: +            lines = render_lines( +                layout.renderable, update_dimensions(region.width, region.height) +            ) +            render_map[layout] = LayoutRender(region, lines) +        return render_map + +    def __rich_console__( +        self, console: Console, options: ConsoleOptions +    ) -> RenderResult: +        with self._lock: +            width = options.max_width or console.width +            height = options.height or console.height +            render_map = self.render(console, options.update_dimensions(width, height)) +            self._render_map = render_map +            layout_lines: List[List[Segment]] = [[] for _ in range(height)] +            _islice = islice +            for region, lines in render_map.values(): +                _x, y, _layout_width, layout_height = region +                for row, line in zip( +                    _islice(layout_lines, y, y + layout_height), lines +                ): +                    row.extend(line) + +            new_line = Segment.line() +            for layout_row in layout_lines: +                yield from layout_row +                yield new_line + + +if __name__ == "__main__": +    from rich.console import Console + +    console = Console() +    layout = Layout() + +    layout.split_column( +        Layout(name="header", size=3), +        Layout(ratio=1, name="main"), +        Layout(size=10, name="footer"), +    ) + +    layout["main"].split_row(Layout(name="side"), Layout(name="body", ratio=2)) + +    layout["body"].split_row(Layout(name="content", ratio=2), Layout(name="s2")) + +    layout["s2"].split_column( +        Layout(name="top"), Layout(name="middle"), Layout(name="bottom") +    ) + +    layout["side"].split_column(Layout(layout.tree, name="left1"), Layout(name="left2")) + +    layout["content"].update("foo") + +    console.print(layout) | 
