Skip to content

xnano.core.rendering

xnano.core.rendering

xnano.core.rendering


Lower components and content primitives into xnano_core render nodes.

Functions:

  • lower_content โ€“

    Lower a renderable into one native render node.

lower_content

lower_content(content: Any) -> CoreRenderNode

Lower a renderable into one native render node.

Parameters:

  • content (Any) โ€“

    String, component, content primitive, or native payload.

Returns:

  • CoreRenderNode โ€“

    A render node accepted by CoreSession.render.

Source code in xnano/core/rendering.py
def lower_content(content: Any) -> core.CoreRenderNode:
    """Lower a renderable into one native render node.

    Args:
        content: String, component, content primitive, or native payload.

    Returns:
        A render node accepted by ``CoreSession.render``.
    """
    grid_fields = getattr(type(content), "_grid_fields", None)
    if isinstance(grid_fields, dict):
        children: list[core.CoreRenderNode] = []
        constraints: list[Any] = []
        for name, field in grid_fields.items():
            value = getattr(content, name, None)
            if value is None or getattr(field, "visible", None) is False:
                continue
            children.append(
                core.CoreRenderNode(
                    content=core.CoreRenderContent.empty(),
                    effect_key=name,
                    constraints=[native.Constraint.fill(1)],
                    children=[lower_content(value)],
                )
            )
            sizing = (
                field.height
                if content._grid_direction == "vertical"
                else field.width
            )
            if sizing is None or sizing.kind in {"fraction", "fill"}:
                constraint = native.Constraint.fill(
                    1 if sizing is None else max(1, sizing.value)
                )
            elif sizing.kind == "cells":
                constraint = native.Constraint.length(sizing.value)
            elif sizing.kind == "percent":
                constraint = native.Constraint.percentage(sizing.value)
            elif sizing.kind == "ratio":
                constraint = native.Constraint.ratio(
                    sizing.value,
                    sizing.denominator,
                )
            else:
                constraint = native.Constraint.min(sizing.minimum or 1)
            constraints.append(constraint)
        factory = (
            core.CoreRenderNode.row
            if content._grid_direction == "horizontal"
            else core.CoreRenderNode.column
        )
        return factory(
            children,
            constraints=constraints,
            gap=content._grid_gap,
        )
    compose = getattr(content, "compose", None)
    if callable(compose):
        from xnano.area import Area
        from xnano.components.component import ComponentRenderContext
        from xnano.core.runtime import get_active_runtime

        runtime = get_active_runtime()
        # A component with responsive compose variants swaps in the one
        # matching the live window width; the plain `compose` handles
        # every tier a component leaves unoverridden. Skipped entirely for
        # content without an override map.
        responsive = getattr(
            type(content), "_component_responsive_composes", None
        )
        if responsive and runtime is not None:
            from xnano.utils.responsive import (
                resolve_responsive_variant,
            )

            variant = resolve_responsive_variant(responsive, runtime.size[0])
            if variant is not None:
                compose = getattr(content, variant)
        width, height = runtime.size if runtime is not None else (0, 0)
        content = compose(
            ComponentRenderContext(
                area=Area(x=0, y=0, width=width, height=height),
            )
        )
    if content is None:
        return core.CoreRenderNode.leaf(core.CoreRenderContent.empty())
    if isinstance(content, str):
        content = TextBlock(text=content)
    if isinstance(content, Run):
        render_ir = core.CoreRenderIR.span(
            content.text,
            get_native_color(content.foreground),
            get_native_color(content.background),
            _native_modifiers(content.modifiers),
        )
    elif isinstance(content, TextBlock):
        if content.lines:
            render_ir = core.CoreRenderIR.paragraph_lines(
                [_line_from_runs(line) for line in content.lines],
                get_native_color(content.foreground),
                get_native_color(content.background),
                _native_modifiers(content.modifiers),
                _ALIGNMENTS[content.horizontal_align],
                content.wrap,
            )
        else:
            render_ir = core.CoreRenderIR.paragraph_raw(
                content.text,
                get_native_color(content.foreground),
                get_native_color(content.background),
                _native_modifiers(content.modifiers),
                _ALIGNMENTS[content.horizontal_align],
                content.wrap,
            )
    elif isinstance(content, Gauge):
        render_ir = core.CoreRenderIR.progress_bar(
            content.progress,
            content.label,
            get_native_color(content.foreground),
            get_native_color(content.background),
        )
    elif isinstance(content, LineGauge):
        render_ir = core.CoreRenderIR.line_gauge(
            content.progress,
            content.label,
            get_native_color(content.foreground),
            get_native_color(content.background),
            get_native_color(content.filled_color),
            get_native_color(content.unfilled_color),
        )
    elif isinstance(content, Bars):
        groups = [
            (
                group.label,
                [
                    (
                        bar.value,
                        bar.label,
                        bar.text_value,
                        get_native_color(bar.color),
                        None,
                        get_native_color(bar.value_color),
                        None,
                    )
                    for bar in group.bars
                ],
            )
            for group in content.groups
        ]
        render_ir = core.CoreRenderIR.bar_chart(
            groups,
            content.bar_width,
            content.bar_gap,
            content.group_gap,
            content.max_value,
            content.direction == "horizontal",
            get_native_color(content.color),
            get_native_color(content.value_color),
            get_native_color(content.label_color),
        )
    elif isinstance(content, Plot):
        return _plot_node(content)
    elif isinstance(content, Sparkline):
        render_ir = core.CoreRenderIR.sparkline(
            list(content.data),
            content.max_value,
            get_native_color(content.foreground),
            get_native_color(content.background),
            get_native_color(content.absent_value_color),
            content.absent_value_symbol,
        )
    elif isinstance(content, Items):
        render_ir = core.CoreRenderIR.list(
            [_line_from_value(item) for item in content.items],
            content.selected,
            get_native_color(content.foreground),
            get_native_color(content.background),
            get_native_color(content.highlight_color),
            get_native_color(content.highlight_background),
            content.highlight_symbol,
        )
    elif isinstance(content, TableGrid):
        columns = max(
            (len(row.cells) for row in content.rows),
            default=(
                len(content.header.cells) if content.header is not None else 1
            ),
        )
        widths = (
            [(2, 1.0)] * columns
            if content.column_widths is None
            else [
                (1, width * 100.0)
                if isinstance(width, float)
                else (0, float(width))
                for width in content.column_widths
            ]
        )
        render_ir = core.CoreRenderIR.table(
            [_table_row(row) for row in content.rows],
            _table_row(content.header) if content.header is not None else None,
            _table_row(content.footer) if content.footer is not None else None,
            widths,
            content.column_spacing,
            content.selected_row,
            content.selected_column,
            get_native_color(content.highlight_color),
            get_native_color(content.highlight_background),
            content.highlight_symbol,
        )
    elif isinstance(content, Scrollbar):
        orientations = {
            "vertical_right": 0,
            "vertical_left": 1,
            "horizontal_bottom": 2,
            "horizontal_top": 3,
        }
        render_ir = core.CoreRenderIR.scrollbar(
            orientations.get(content.orientation, 0),
            content.content_length,
            content.position,
            content.viewport_length,
            get_native_color(content.color),
            get_native_color(content.thumb_color),
            get_native_color(content.track_color),
            content.begin_symbol,
            content.end_symbol,
        )
    elif isinstance(content, Clear):
        render_ir = core.CoreRenderIR.clear()
    elif isinstance(content, CellCanvas):
        render_ir = _cell_canvas_ir(content)
    elif isinstance(content, Canvas):
        render_ir = core.CoreRenderIR.canvas(
            [_canvas_shape(shape) for shape in content.shapes],
            content.x_bounds,
            content.y_bounds,
            get_native_color(content.background),
            _MARKERS[content.marker],
        )
    elif isinstance(content, Native):
        if isinstance(content.payload, core.CoreRenderNode):
            return content.payload
        if isinstance(content.payload, core.CoreRenderIR):
            render_ir = content.payload
        else:
            render_ir = core.CoreRenderIR.paragraph_raw(str(content.payload))
    elif isinstance(content, Stack):
        children = [lower_content(child) for child in content.children]
        factory = (
            core.CoreRenderNode.row
            if content.direction == "horizontal"
            else core.CoreRenderNode.column
        )
        return factory(
            children,
            constraints=[native.Constraint.fill(1)] * len(children),
            gap=content.gap,
        )
    elif isinstance(content, Panel):
        from xnano.area import Padding

        block = native.Block.default()
        sides = content.border_sides
        if content.border is not None or sides:
            borders = native.Borders.ALL
            if sides:
                borders = native.Borders.NONE
                for side in sides:
                    borders = borders | _BORDER_SIDES[side]
            block = block.borders(borders).border_type(
                _BORDER_TYPES[content.border or "plain"]
            )
        if content.title is not None:
            block = (
                block.title_bottom(content.title)
                if content.title_position == "bottom"
                else block.title_top(content.title)
            )
        border_color = get_native_color(content.border_color)
        if border_color is not None:
            block = block.border_style(native.Style.default().fg(border_color))
        background = get_native_color(content.background)
        if background is not None:
            block = block.style(native.Style.default().bg(background))
        padding = Padding.parse(content.padding)
        block = block.padding(
            native.Padding.new(
                padding.left,
                padding.right,
                padding.top,
                padding.bottom,
            )
        )
        horizontal_border = int(
            content.border is not None
            or bool(sides and ("left" in sides or "right" in sides))
        )
        vertical_border = int(
            content.border is not None
            or bool(sides and ("top" in sides or "bottom" in sides))
        )
        return core.CoreRenderNode(
            content=core.CoreRenderContent.widget(block),
            direction=native.Direction.Vertical,
            constraints=[native.Constraint.fill(1)],
            margin=native.Margin(
                max(padding.left, padding.right) + horizontal_border,
                max(padding.top, padding.bottom) + vertical_border,
            ),
            children=[lower_content(content.child)],
            z=content.z,
            visible=content.visible,
        )
    else:
        render_ir = core.CoreRenderIR.paragraph_raw(str(content))
    return core.CoreRenderNode(
        content=core.CoreRenderContent.ir(render_ir),
        z=getattr(content, "z", 0),
        visible=getattr(content, "visible", True),
    )