- FRC Game Manual
- FRC Game Q&A

# Creating A Widget

Widgets allow us to view, change, and interact with data published through different data sources. The CameraServer, NetworkTables, and Base plugins provide the widgets to control basic data types (including FRC-specific data types). However, custom widgets allow us to control our custom data types we made in the previous sections or Java Objects.

The basic `Widget` interface inherits from the `Component` and `Sourced` interfaces. `Component` is the most basic building block of components that be displayed in Shuffleboard. `Sourced` is an interface for things that can handle and interface with data sources to display or modify data. Widgets that don’t support data bindings but simply have child nodes would not use the `Sourced` interface but simply the `Component` interface. Both are basic building blocks towards making widgets and allows us to modify and display data.

A good widget allows the end-user to customize the widget to suit their needs. An example could be to allow the user to control the range of the number slider, that is, its maximum and minimum or the orientation of the slider itself. The view of the widget or how it looks is defined using FXML. `FXML` is an XML based language that is useful for defining the static layout of the widget (Panes, Labels and Controls).

More about FXML can be found here.

## Defining a Widget’s FXML

In this example, we will create two sliders to help us control the X and Y coordinates of our Point2D data type we created in previous sections. It is helpful to place the FXML file in the same package as the Java class.

In order to create an empty, blank window for our widget, we need to create a `Pane`. A Pane is a parent node that contains other child nodes, in this case, 2 sliders. There are many different types of Pane, they are as noted:

- Stack Pane

    - Stack Panes allow elements to be overlaid. Also, StackPanes by default center child nodes.

- Grid Pane

    - Grid Panes are extremely useful defining child elements using a coordinate system by creating a flexible grid of rows and columns on the pane.

- Flow Pane

    - Flow Panes wrap all child nodes at a boundary set. Child nodes can flow vertically (wrapped at the height boundary for the pane) or horizontally (wrapped at the width boundary of the pane).

- Anchor Pane

    - Anchor Panes allow child elements to be placed in the top, bottom, left side, right side, or center of the pane.

Layout panes are also extremely useful for placing child nodes in one horizontal row using a HBox or one vertical column using a VBox.

The basic syntax for defining a Pane using FXML would be as the following:

    <?import javafx.scene.layout.*?>
    <StackPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="/path/to/widget/class" fx:id="root">
       ...
    </StackPane>

The `fx:controller` attribute contains the name of the widget class. An instance of this class is created when the FXML file is loaded. For this to work, the controller class must have a no-argument constructor.

## Creating A Widget Class

Now that we have a Pane, we can now add child elements to that pane. In this example, we can add two slider objects. Remember to add an `fx:id` to each element so they can be referenced in our Java class we will make later on. We will use a `VBox` to position our slider on top of each other.

    <?import javafx.scene.layout.*?>
    <StackPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="/path/to/widget/class" fx:id="root">
       <VBox>
          <Slider fx:id = "xSlider"/>
          <Slider fx:id = "ySlider"/>
       </VBox>
    </StackPane>

Now that we have finished creating our FXML file, we can now create a widget class. The widget class should include a `@Description` annotation that states the supported data types of the widget and the name of the widget. If a `@Description` annotation is not present, the plugin class must implement the `get()` method to return its widgets.

It also must include a `@ParametrizedController` annotation that points to the FXML file containing the layout of the widget. If the class that only supports one data source it must extend the `SimpleAnnotatedWidget` class. If the class supports multiple data sources, it must extend the `ComplexAnnotatedWidget` class. For more information, see Widget Types.

    import edu.wpi.first.shuffleboard.api.widget.Description;
    import edu.wpi.first.shuffleboard.api.widget.ParametrizedController;
    import edu.wpi.first.shuffleboard.api.widget.SimpleAnnotatedWidget;
    /*
     * If the FXML file and Java file are in the same package, that is the Java file is in src/main/java and the
     * FXML file is under src/main/resources or your code equivalent package, the relative path will work
     * However, if they are in different packages, an absolute path will be required.
    */
    @Description(name = "MyPoint2D", dataTypes = MyPoint2D.class)
    @ParametrizedController("Point2DWidget.fxml")
    public final class Point2DWidget extends SimpleAnnotatedWidget<MyPoint2D> {
    }

If you are not using a custom data type, you can reference any Java data type (ie. `Double.class`), or if the widget does not need data binding you can pass `NoneType.class`.

Now that we have created our class we can create fields for the widgets we declared in our FXML file using the `@FXML` annotation. For our two sliders, an example would be:

    import edu.wpi.first.shuffleboard.api.widget.Description;
    import edu.wpi.first.shuffleboard.api.widget.ParametrizedController;
    import edu.wpi.first.shuffleboard.api.widget.SimpleAnnotatedWidget;
    import javafx.fxml.FXML;
    @Description(name = "MyPoint2D", dataTypes = MyPoint2D.class)
    @ParametrizedController("Point2DWidget.fxml")
    public final class Point2DWidget extends SimpleAnnotatedWidget<MyPoint2D> {
       @FXML
       private Pane root;
       @FXML
       private Slider xSlider;
       @FXML
       private Slider ySlider;
    }

In order to display our pane on our custom widget we need to override the `getView()` method and return our `StackPane`.

    import edu.wpi.first.shuffleboard.api.widget.Description;
    import edu.wpi.first.shuffleboard.api.widget.ParametrizedController;
    import edu.wpi.first.shuffleboard.api.widget.SimpleAnnotatedWidget;
    import javafx.fxml.FXML;
    @Description(name = "MyPoint2D", dataTypes = MyPoint2D.class)
    @ParametrizedController("Point2DWidget.fxml")
    public final class Point2DWidget extends SimpleAnnotatedWidget<MyPoint2D> {
       @FXML
       private StackPane root;
       @FXML
       private Slider xSlider;
       @FXML
       private Slider ySlider;
       @Override
       public Pane getView() {
          return root;
       }
    }

## Binding Elements and Adding Listeners

Binding is a mechanism that allows JavaFX widgets to express direct relationships with the data source. For example, changing a widget will change its related NetworkTableEntry and vice versa.

An example, in this case, would be changing the X and Y coordinate of our 2D point by changing the values of xSlider and ySlider respectively.

A good practice is to set bindings in the `initialize()` method tagged with the `@FXML` annotation which is required to call the method from FXML if the method is not `public`.

    import edu.wpi.first.shuffleboard.api.widget.Description;
    import edu.wpi.first.shuffleboard.api.widget.ParametrizedController;
    import edu.wpi.first.shuffleboard.api.widget.SimpleAnnotatedWidget;
    import javafx.fxml.FXML;
    @Description(name = "MyPoint2D", dataTypes = MyPoint2D.class)
    @ParametrizedController("Point2DWidget.fxml")
    public final class Point2DWidget extends SimpleAnnotatedWidget<MyPoint2D> {
       @FXML
       private StackPane root;
       @FXML
       private Slider xSlider;
       @FXML
       private Slider ySlider;
       @FXML
       private void initialize() {
          xSlider.valueProperty().bind(dataOrDefault.map(MyPoint2D::getX));
          ySlider.valueProperty().bind(dataOrDefault.map(MyPoint2D::getY));
       }
       @Override
       public Pane getView() {
          return root;
       }
     }

The above `initialize` method binds the slider’s value property to the `MyPoint2D` data class’ corresponding X and Y value. Meaning, changing the slider will change the coordinate and vice versa. The `dataOrDefault.map()` method will get the data source’s value, or, if no source is present, will return the default value.

Using a listener is another way to change values when the slider or data source has changed. For example a listener for our slider would be:

    xSlider.valueProperty().addListener((observable, oldValue, newValue) -> setData(getData().withX(newValue));

In this case, the `setData()` method sets the value in the data source of the widget to the `newValue`.

## Exploring Custom Components

Widgets are not automatically discovered when loading plugins; the defining plugin must explicitly export it for it to be usable. This approach is taken to allow multiple plugins to be defined in the same JAR.

    @Override
    public List<ComponentType> getComponents() {
      return List.of(WidgetType.forAnnotatedWidget(Point2DWidget.class));
    }

## Set Default Widget For Data type

In order to set your widget as default for your custom data type, you can override the `getDefaultComponents()` in your plugin class that stores a Map for all default widgets as noted below:

    @Override
    public Map<DataType, ComponentType> getDefaultComponents() {
       return Map.of(Point2DType.Instance, WidgetType.forAnnotatedWidget(Point2DWidget.class));
    }