Skip to content

Tutorial: Custom Device UI

Dev Null edited this page May 12, 2022 · 3 revisions

By default, all LX pattern and effect devices will have a generic UI constructed which provides knob controls and color pickers for all the relevant device parameters. However, you may wish to implement your own custom UI layout.

Defining a Custom UI

Any device class which implements the UIDeviceControls<> interface will have a method invoked to construct the controls for this device in the bottom pane of the LX Studio UI. Reference the example below.

Many useful helper methods are available in the UIDeviceControls interface.

public class AppPatternWithUI extends LXPattern implements UIDeviceControls<AppPatternWithUI> {

  public final DiscreteParameter number =
    new DiscreteParameter("Number", 100)
    .setDescription("A numeric parameter");

  public final ObjectParameter<String> string =
    new ObjectParameter<String>("String", new String[] { "One", "Two", "Three" })
    .setDescription("A string parameter");

  public final CompoundParameter knob =
    new CompoundParameter("Knob", 0, 1)
    .setDescription("A knob parameter");

  public AppPatternWithUI(LX lx) {
    super(lx);
    addParameter("number", this.number);
    addParameter("string", this.string);
    addParameter("knob", this.knob);
  }

  @Override
  protected void run(double deltaMs) {
    for (LXPoint p : model.points) {
      colors[p.index] = LXColor.hsb(240, 100, 100);
    }
  }

The key method is the buildDeviceControls method:

  /**
   * Implement this method from the UIDeviceControls interface to build a custom UI for
   * your pattern device, rather than relying upon the in-built default UI.
   */
  @Override
  public void buildDeviceControls(UI ui, UIDevice uiDevice, AppPatternWithUI pattern) {
    uiDevice.setContentWidth(COL_WIDTH);
    addColumn(uiDevice, "Custom",
      newIntegerBox(pattern.number),
      controlLabel(ui, "Number"),
      newDropMenu(pattern.string),
      controlLabel(ui, "String"),
      newKnob(pattern.knob)
    );
  }

}

Horizontal Containers

If you need more elements than can fit in a single column, elements can be placed in rows using a horizontal container.

import heronarts.p4lx.ui.UI2dContainer;
import heronarts.p4lx.ui.component.UIKnob;

// ...

  @Override
  public void buildDeviceControls(UI ui, UIDevice uiDevice, AppPatternWithUI pattern) {
    uiDevice.setContentWidth(COL_WIDTH * 3);
    addColumn(uiDevice, COL_WIDTH * 3,
      UI2dContainer.newHorizontalContainer(UIKnob.HEIGHT, 0,
        new UIKnob(pattern.knob1),
        new UIKnob(pattern.knob2),
        new UIKnob(pattern.knob3),
        new UIKnob(pattern.knob4)
      ),
      UI2dContainer.newHorizontalContainer(UIKnob.HEIGHT, 0,
        new UIKnob(pattern.knob5), 
        new UIKnob(pattern.knob6)
      )
    );
  }

Don't forget to register these in the constructor, otherwise they won't be mappable.

  public AppPatternWithUI(LX lx) {
    super(lx);
    addParameter("knob1", this.knob1);
    addParameter("knob2", this.knob2);
    ...
  }