How to Create Combined Widget Layouts

View Source

Arrange BB Kino widgets in custom layouts for different use cases.

Prerequisites

Using Kino.Layout

BB Kino widgets are standard Kino outputs. Combine them with Kino.Layout.grid/2:

Kino.Layout.grid([
  BB.Kino.safety(MyRobot),
  BB.Kino.joints(MyRobot),
  BB.Kino.visualisation(MyRobot)
], columns: 3)

Common Layout Patterns

Control Panel (Left) + Visualisation (Right)

Kino.Layout.grid([
  Kino.Layout.grid([
    BB.Kino.safety(MyRobot),
    BB.Kino.joints(MyRobot),
    BB.Kino.commands(MyRobot)
  ], columns: 1),
  BB.Kino.visualisation(MyRobot)
], columns: 2)

Full Dashboard

Kino.Layout.grid([
  # Top row: safety and joints
  Kino.Layout.grid([
    BB.Kino.safety(MyRobot),
    BB.Kino.joints(MyRobot)
  ], columns: 2),

  # Middle row: visualisation and events
  Kino.Layout.grid([
    BB.Kino.visualisation(MyRobot),
    BB.Kino.events(MyRobot)
  ], columns: 2),

  # Bottom row: commands
  BB.Kino.commands(MyRobot)
], columns: 1)

Monitoring Only (No Control)

Kino.Layout.grid([
  BB.Kino.visualisation(MyRobot),
  BB.Kino.events(MyRobot)
], columns: 2)

Minimal Control

Kino.Layout.grid([
  BB.Kino.safety(MyRobot),
  BB.Kino.joints(MyRobot)
], columns: 1)

Nested Layouts

Create complex arrangements with nested grids:

left_panel = Kino.Layout.grid([
  BB.Kino.safety(MyRobot),
  BB.Kino.joints(MyRobot)
], columns: 1)

right_panel = Kino.Layout.grid([
  BB.Kino.visualisation(MyRobot),
  BB.Kino.events(MyRobot)
], columns: 1)

Kino.Layout.grid([left_panel, right_panel], columns: 2)

Adding Labels

Use Kino.Markdown for section headers:

Kino.Layout.grid([
  Kino.Markdown.new("## Control"),
  BB.Kino.safety(MyRobot),
  BB.Kino.joints(MyRobot),
  Kino.Markdown.new("## Monitoring"),
  BB.Kino.events(MyRobot)
], columns: 1)

Conditional Widgets

Show different widgets based on conditions:

widgets = [BB.Kino.safety(MyRobot)]

widgets =
  if has_joints?(MyRobot) do
    widgets ++ [BB.Kino.joints(MyRobot)]
  else
    widgets
  end

widgets =
  if has_commands?(MyRobot) do
    widgets ++ [BB.Kino.commands(MyRobot)]
  else
    widgets
  end

Kino.Layout.grid(widgets, columns: 1)

Using Tabs

Organise widgets in tabs with Kino.Layout.tabs/1:

Kino.Layout.tabs([
  Control: Kino.Layout.grid([
    BB.Kino.safety(MyRobot),
    BB.Kino.joints(MyRobot)
  ], columns: 1),
  Visualisation: BB.Kino.visualisation(MyRobot),
  Events: BB.Kino.events(MyRobot),
  Commands: BB.Kino.commands(MyRobot)
])

Multiple Robots

Control multiple robots in one notebook:

Kino.Layout.grid([
  Kino.Markdown.new("## Robot 1"),
  Kino.Layout.grid([
    BB.Kino.safety(Robot1),
    BB.Kino.joints(Robot1)
  ], columns: 2),

  Kino.Markdown.new("## Robot 2"),
  Kino.Layout.grid([
    BB.Kino.safety(Robot2),
    BB.Kino.joints(Robot2)
  ], columns: 2)
], columns: 1)

Responsive Considerations

Livebook cells have a fixed width. For complex layouts:

  • Use 2-3 columns maximum
  • Place controls on the left
  • Put visualisation in a wider column
  • Test at different zoom levels

Performance Tips

  • Events widget can be resource-intensive with high message rates - add path filters
  • Visualisation updates are throttled by default
  • For many joints, consider a more compact layout

Saving Layouts as Functions

Create reusable layout functions:

defmodule MyLayouts do
  def control_panel(robot) do
    Kino.Layout.grid([
      BB.Kino.safety(robot),
      BB.Kino.joints(robot)
    ], columns: 1)
  end

  def full_dashboard(robot) do
    Kino.Layout.grid([
      control_panel(robot),
      Kino.Layout.grid([
        BB.Kino.visualisation(robot),
        BB.Kino.events(robot)
      ], columns: 1)
    ], columns: 2)
  end
end

MyLayouts.full_dashboard(MyRobot)