Packages

Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.

Current section

Files

Jump to
raxol lib raxol ui rendering painter.ex
Raw

lib/raxol/ui/rendering/painter.ex

defmodule Raxol.UI.Rendering.Painter do
@moduledoc """
Handles painting of UI components to the terminal.
"""
require Raxol.Core.Runtime.Log
@doc """
Paints the render tree into draw commands or buffer updates.
Currently a stub; in the future, this will convert the render tree into draw commands,
potentially processing diffs or composition-specific changes.
"""
@spec paint(
compose_stage_output :: any(),
new_tree_for_reference :: map() | nil,
previous_composed_tree :: any(),
previous_painted_output :: list(map()) | nil
) :: list(map())
def paint(
composed_data,
_new_tree_for_reference,
previous_composed_tree,
previous_painted_output
) do
# If the composed_data is identical to the previous composed tree, reuse the previous painted output
handle_paint_reuse(
composed_data === previous_composed_tree &&
previous_painted_output != nil,
composed_data,
previous_composed_tree,
previous_painted_output
)
end
defp do_paint_node(nil, _parent_x, _parent_y), do: []
defp do_paint_node(composed_node, _parent_x_offset, _parent_y_offset)
when not is_map(composed_node) do
Raxol.Core.Runtime.Log.warning(
"Paint Stage: Encountered non-map node, expected composed map structure: #{inspect(composed_node)}"
)
[]
end
defp do_paint_node(composed_node, parent_x_offset, parent_y_offset) do
paint_ops_for_current_node =
paint_current_node(composed_node, parent_x_offset, parent_y_offset)
children_paint_ops =
paint_children(composed_node, parent_x_offset, parent_y_offset)
paint_ops_for_current_node ++ children_paint_ops
end
defp paint_current_node(composed_node, parent_x_offset, parent_y_offset) do
case composed_node[:composed_type] do
:composed_element ->
paint_composed_element(composed_node)
:primitive ->
paint_primitive(composed_node, parent_x_offset, parent_y_offset)
:unprocessed_map_wrapper ->
[]
unknown_type ->
Raxol.Core.Runtime.Log.warning(
"Paint Stage: Unknown composed_type: #{inspect(unknown_type)}"
)
[]
end
end
defp paint_composed_element(composed_node) do
attrs = composed_node[:attributes] || %{x: 0, y: 0, width: 0, height: 0}
original_type = composed_node[:original_type]
properties = composed_node[:properties] || %{}
paint_op = %{
op: :draw_element,
element_type: original_type,
x: attrs.x,
y: attrs.y,
width: attrs.width,
height: attrs.height,
properties: properties,
text_content:
properties[:text] || properties[:label] || properties[:value]
}
Raxol.Core.Runtime.Log.debug(
"Paint Stage: Generated draw_element op for #{original_type}: #{inspect(paint_op)}"
)
[paint_op]
end
defp paint_primitive(composed_node, parent_x_offset, parent_y_offset) do
value = composed_node[:value]
paint_op = %{
op: :draw_primitive,
value: value,
x: parent_x_offset,
y: parent_y_offset,
primitive_type: get_primitive_type(value)
}
Raxol.Core.Runtime.Log.debug(
"Paint Stage: Generated draw_primitive op: #{inspect(paint_op)}"
)
[paint_op]
end
defp get_primitive_type(value) do
determine_primitive_type(value)
end
defp paint_children(composed_node, parent_x_offset, parent_y_offset) do
(composed_node[:children] || [])
|> Enum.flat_map(fn child_node ->
{child_parent_x, child_parent_y} =
get_child_parent_offsets(
composed_node,
parent_x_offset,
parent_y_offset
)
do_paint_node(child_node, child_parent_x, child_parent_y)
end)
end
defp get_child_parent_offsets(composed_node, parent_x_offset, parent_y_offset) do
handle_element_offsets(
composed_node[:composed_type] == :composed_element,
composed_node,
parent_x_offset,
parent_y_offset
)
end
# Helper functions for if-statement elimination
defp handle_paint_reuse(
true,
_composed_data,
_previous_composed_tree,
previous_painted_output
) do
Raxol.Core.Runtime.Log.debug(
"Paint Stage: Reusing previous_painted_output as composed_data is identical to previous_composed_tree."
)
previous_painted_output
end
defp handle_paint_reuse(
false,
composed_data,
previous_composed_tree,
_previous_painted_output
) do
log_repainting_reason(
composed_data === previous_composed_tree,
composed_data,
previous_composed_tree
)
Raxol.Core.Runtime.Log.debug(
"Paint Stage: Starting with composed_stage_output: #{inspect(composed_data)}"
)
# Initial parent offsets are 0,0
do_paint_node(composed_data, 0, 0)
end
defp log_repainting_reason(true, _composed_data, _previous_composed_tree) do
Raxol.Core.Runtime.Log.debug(
"Paint Stage: composed_data is identical, but no previous_painted_output to reuse. Repainting."
)
end
defp log_repainting_reason(false, composed_data, previous_composed_tree) do
Raxol.Core.Runtime.Log.debug(
"Paint Stage: composed_data differs from previous_composed_tree or no previous. Repainting. Details: composed_data: #{inspect(composed_data)}, prev_composed_tree: #{inspect(previous_composed_tree)}"
)
end
defp determine_primitive_type(value) when is_binary(value), do: :text
defp determine_primitive_type(value) when is_number(value), do: :number
defp determine_primitive_type(_value), do: :unknown
defp handle_element_offsets(
true,
composed_node,
_parent_x_offset,
_parent_y_offset
) do
attrs = composed_node[:attributes] || %{}
{attrs[:x] || 0, attrs[:y] || 0}
end
defp handle_element_offsets(
false,
_composed_node,
parent_x_offset,
parent_y_offset
) do
{parent_x_offset, parent_y_offset}
end
end