Skip to main content

lib/bb/tui/panels/visualization.ex

defmodule BB.TUI.Panels.Visualization do
  @moduledoc """
  Visualization tab — renders the live robot in 3D via `ExRatatui.Widgets.Viewport3D`.

  The scene is rebuilt from the current joint positions each render, so the robot
  animates as sensor data arrives. Pure function: state + main `Rect` ->
  `[{widget, rect}]`.
  """

  alias BB.TUI.State
  alias BB.TUI.Theme
  alias BB.TUI.Viz.RobotScene
  alias ExRatatui.Style
  alias ExRatatui.Text.{Line, Span}
  alias ExRatatui.Widgets.{Block, Viewport3D}

  @spec render_panes(State.t(), struct()) :: [{struct(), struct()}]
  def render_panes(%State{robot_struct: robot} = state, area) do
    scene = RobotScene.build(robot, positions(state))
    mode = State.viz_render_mode(state)

    # `render_mode` (cycled with `m`) is `:auto` by default — crisp pixel graphics
    # (Kitty/Sixel) on capable terminals, braille fallback elsewhere. The active
    # mode is shown dim in the title so it is clear what is on screen.
    widget = %Viewport3D{
      scene: scene,
      camera: State.viz_camera(state),
      render_mode: mode,
      pipeline: :rasterize,
      block: %Block{
        title: title_line(mode),
        borders: [:all],
        border_type: :rounded,
        border_style: Theme.border_style(true)
      }
    }

    [{widget, area}]
  end

  defp title_line(mode) do
    %Line{
      spans: [
        %Span{content: "Robot "},
        %Span{content: "[#{mode}]", style: %Style{fg: Theme.dim_text()}}
      ]
    }
  end

  defp positions(%State{joints: %{entries: entries}}) do
    Map.new(entries, fn {name, %{position: pos}} -> {name, pos} end)
  end
end