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),
)