diff --git a/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/BasePlugin.java b/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/BasePlugin.java index b4e82abd0..88ebac9e9 100644 --- a/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/BasePlugin.java +++ b/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/BasePlugin.java @@ -47,6 +47,7 @@ import edu.wpi.first.shuffleboard.plugin.base.widget.AlertsWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.BasicFmsInfoWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.BasicSubsystemWidget; +import edu.wpi.first.shuffleboard.plugin.base.widget.BooleanArrayWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.BooleanBoxWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.ComboBoxChooserWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.CommandWidget; @@ -56,17 +57,19 @@ import edu.wpi.first.shuffleboard.plugin.base.widget.GraphWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.GyroWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.MecanumDriveWidget; +import edu.wpi.first.shuffleboard.plugin.base.widget.NumberArrayWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.NumberBarWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.NumberSliderWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.PIDCommandWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.PIDControllerWidget; -import edu.wpi.first.shuffleboard.plugin.base.widget.ProfiledPIDControllerWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.PowerDistributionPanelWidget; +import edu.wpi.first.shuffleboard.plugin.base.widget.ProfiledPIDControllerWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.RelayWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.RobotPreferencesWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.SimpleDialWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.SpeedControllerWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.SplitButtonChooserWidget; +import edu.wpi.first.shuffleboard.plugin.base.widget.StringArrayWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.TextViewWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.ThreeAxisAccelerometerWidget; import edu.wpi.first.shuffleboard.plugin.base.widget.ToggleButtonWidget; @@ -83,7 +86,7 @@ @Description( group = "edu.wpi.first.shuffleboard", name = "Base", - version = "1.3.7", + version = "1.4.0", summary = "Defines all the WPILib data types and stock widgets" ) @SuppressWarnings("PMD.CouplingBetweenObjects") @@ -169,6 +172,9 @@ public List getComponents() { WidgetType.forAnnotatedWidget(UltrasonicWidget.class), WidgetType.forAnnotatedWidget(FieldWidget.class), WidgetType.forAnnotatedWidget(AlertsWidget.class), + WidgetType.forAnnotatedWidget(BooleanArrayWidget.class), + WidgetType.forAnnotatedWidget(NumberArrayWidget.class), + WidgetType.forAnnotatedWidget(StringArrayWidget.class), new LayoutClass<>("List Layout", ListLayout.class), new LayoutClass<>("Grid Layout", GridLayout.class), createSubsystemLayoutType() @@ -204,6 +210,9 @@ public Map getDefaultComponents() { .put(FieldType.Instance, WidgetType.forAnnotatedWidget(FieldWidget.class)) .put(AlertsType.Instance, WidgetType.forAnnotatedWidget(AlertsWidget.class)) .put(SubsystemType.Instance, createSubsystemLayoutType()) + .put(DataTypes.BooleanArray, WidgetType.forAnnotatedWidget(BooleanArrayWidget.class)) + .put(DataTypes.NumberArray, WidgetType.forAnnotatedWidget(NumberArrayWidget.class)) + .put(DataTypes.StringArray, WidgetType.forAnnotatedWidget(StringArrayWidget.class)) .build(); } diff --git a/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/control/ArrayTableView.java b/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/control/ArrayTableView.java new file mode 100644 index 000000000..d14fae6fc --- /dev/null +++ b/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/control/ArrayTableView.java @@ -0,0 +1,74 @@ +package edu.wpi.first.shuffleboard.plugin.base.control; + + +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.property.ReadOnlyStringWrapper; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.Label; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.util.Callback; + +import java.util.ArrayList; + +/** + * A table view that displays an array {@code T[]} as a table with an index and a value column. + * @param the type of each item in the array (e.g. {@code Boolean} for {@code Boolean[]}) + */ +public class ArrayTableView extends TableView> { + private final TableColumn, String> indexCol = new TableColumn<>("Index"); + private final TableColumn, T> valueCol = new TableColumn<>("Value"); + + private final ObservableList> list = FXCollections.observableArrayList(); + + @SuppressWarnings("JavadocMethod") + public ArrayTableView() { + super(); + + indexCol.setCellValueFactory(p -> new ReadOnlyStringWrapper(String.valueOf(p.getValue().index))); + indexCol.setResizable(false); + indexCol.setPrefWidth(50); + + valueCol.setCellValueFactory(p -> new ReadOnlyObjectWrapper<>(p.getValue().value)); + valueCol.prefWidthProperty().bind(widthProperty().subtract(50).subtract(2)); + + //noinspection unchecked + getColumns().addAll(indexCol, valueCol); + + setItems(list); + + Label placeholder = new Label("No data available"); + setPlaceholder(placeholder); + } + + public void setValueCellFactory( + Callback, T>, TableCell, T>> callback + ) { + valueCol.setCellFactory(callback); + } + + /** + * Convenience method to set the table's items directly, instead of using an {@link ObservableList}. + */ + public void setItems(T[] items) { + final var entries = new ArrayList>(); + + for (int i = 0; i < items.length; i++) { + entries.add(new ArrayTableEntry<>(i, items[i])); + } + + list.setAll(entries); + } + + public static class ArrayTableEntry { + public final int index; + public final S value; + + public ArrayTableEntry(int index, S value) { + this.index = index; + this.value = value; + } + } +} diff --git a/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/widget/BooleanArrayWidget.java b/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/widget/BooleanArrayWidget.java new file mode 100644 index 000000000..db96d0eb5 --- /dev/null +++ b/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/widget/BooleanArrayWidget.java @@ -0,0 +1,76 @@ +package edu.wpi.first.shuffleboard.plugin.base.widget; + +import edu.wpi.first.shuffleboard.api.prefs.Group; +import edu.wpi.first.shuffleboard.api.prefs.Setting; +import edu.wpi.first.shuffleboard.api.widget.Description; +import edu.wpi.first.shuffleboard.api.widget.SimpleAnnotatedWidget; +import edu.wpi.first.shuffleboard.plugin.base.control.ArrayTableView; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.control.TableCell; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; + +import java.util.List; +import java.util.stream.IntStream; + +@Description( + name = "Boolean Array", + dataTypes = boolean[].class, + summary = "Displays an array of booleans" +) +public final class BooleanArrayWidget extends SimpleAnnotatedWidget { + private final StackPane pane = new StackPane(); + private final ArrayTableView table = new ArrayTableView<>(); + + private final Property trueColor + = new SimpleObjectProperty<>(this, "colorWhenTrue", Color.LAWNGREEN); + private final Property falseColor + = new SimpleObjectProperty<>(this, "colorWhenFalse", Color.DARKRED); + + @SuppressWarnings("JavadocMethod") + public BooleanArrayWidget() { + pane.getChildren().add(table); + + table.setValueCellFactory(tableColumn -> new BooleanTableCell<>()); + + dataOrDefault.addListener((observableValue, oldBooleans, newBooleans) -> { + final var array = new Boolean[newBooleans.length]; + IntStream.range(0, newBooleans.length).forEach(i -> array[i] = newBooleans[i]); + table.setItems(array); + }); + } + + @Override + public List getSettings() { + return List.of( + Group.of("Colors", + Setting.of("Color when true", "The color to use when a value is `true`", trueColor, Color.class), + Setting.of("Color when false", "The color to use when a value is `false`", falseColor, Color.class) + ) + ); + } + + @Override + public Pane getView() { + return pane; + } + + private class BooleanTableCell extends TableCell { + private Background createBooleanBackground(boolean value) { + return new Background(new BackgroundFill(value ? trueColor.getValue() : falseColor.getValue(), null, null)); + } + + @Override + protected void updateItem(Boolean t, boolean empty) { + if (empty || t == null) { + setBackground(null); + } else { + setBackground(createBooleanBackground(t)); + } + } + } +} diff --git a/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/widget/NumberArrayWidget.java b/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/widget/NumberArrayWidget.java new file mode 100644 index 000000000..8051fe49a --- /dev/null +++ b/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/widget/NumberArrayWidget.java @@ -0,0 +1,35 @@ +package edu.wpi.first.shuffleboard.plugin.base.widget; + +import edu.wpi.first.shuffleboard.api.widget.Description; +import edu.wpi.first.shuffleboard.api.widget.SimpleAnnotatedWidget; +import edu.wpi.first.shuffleboard.plugin.base.control.ArrayTableView; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; + +import java.util.stream.IntStream; + +@Description( + name = "Number Array", + dataTypes = double[].class, + summary = "Displays an array of numbers" +) +public final class NumberArrayWidget extends SimpleAnnotatedWidget { + private final StackPane pane = new StackPane(); + private final ArrayTableView table = new ArrayTableView<>(); + + @SuppressWarnings("JavadocMethod") + public NumberArrayWidget() { + pane.getChildren().add(table); + + dataOrDefault.addListener((observableValue, oldDoubles, newDoubles) -> { + final var array = new Double[newDoubles.length]; + IntStream.range(0, newDoubles.length).forEach(i -> array[i] = newDoubles[i]); + table.setItems(array); + }); + } + + @Override + public Pane getView() { + return pane; + } +} diff --git a/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/widget/StringArrayWidget.java b/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/widget/StringArrayWidget.java new file mode 100644 index 000000000..416d7f511 --- /dev/null +++ b/plugins/base/src/main/java/edu/wpi/first/shuffleboard/plugin/base/widget/StringArrayWidget.java @@ -0,0 +1,29 @@ +package edu.wpi.first.shuffleboard.plugin.base.widget; + +import edu.wpi.first.shuffleboard.api.widget.Description; +import edu.wpi.first.shuffleboard.api.widget.SimpleAnnotatedWidget; +import edu.wpi.first.shuffleboard.plugin.base.control.ArrayTableView; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; + +@Description( + name = "String Array", + dataTypes = String[].class, + summary = "Displays an array of strings" +) +public final class StringArrayWidget extends SimpleAnnotatedWidget { + private final StackPane pane = new StackPane(); + private final ArrayTableView table = new ArrayTableView<>(); + + @SuppressWarnings("JavadocMethod") + public StringArrayWidget() { + pane.getChildren().add(table); + + dataOrDefault.addListener((observableValue, oldStrings, newStrings) -> table.setItems(newStrings)); + } + + @Override + public Pane getView() { + return pane; + } +}