Add fluent builders for more flexibly adding data to Shuffleboard (#1022)

This commit is contained in:
Sam Carlberg
2018-09-28 04:18:18 -04:00
committed by Peter Johnson
parent ac7dfa5042
commit 175c6c1f01
51 changed files with 3120 additions and 0 deletions

View File

@@ -9,6 +9,7 @@ package edu.wpi.first.wpilibj;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.wpilibj.livewindow.LiveWindow;
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
/**
@@ -200,6 +201,7 @@ public abstract class IterativeRobotBase extends RobotBase {
// or from power-on.
if (m_lastMode != Mode.kDisabled) {
LiveWindow.setEnabled(false);
Shuffleboard.disableActuatorWidgets();
disabledInit();
m_watchdog.addEpoch("disabledInit()");
m_lastMode = Mode.kDisabled;
@@ -213,6 +215,7 @@ public abstract class IterativeRobotBase extends RobotBase {
// mode or from power-on.
if (m_lastMode != Mode.kAutonomous) {
LiveWindow.setEnabled(false);
Shuffleboard.disableActuatorWidgets();
autonomousInit();
m_watchdog.addEpoch("autonomousInit()");
m_lastMode = Mode.kAutonomous;
@@ -226,6 +229,7 @@ public abstract class IterativeRobotBase extends RobotBase {
// from power-on.
if (m_lastMode != Mode.kTeleop) {
LiveWindow.setEnabled(false);
Shuffleboard.disableActuatorWidgets();
teleopInit();
m_watchdog.addEpoch("teleopInit()");
m_lastMode = Mode.kTeleop;
@@ -239,6 +243,7 @@ public abstract class IterativeRobotBase extends RobotBase {
// power-on.
if (m_lastMode != Mode.kTest) {
LiveWindow.setEnabled(true);
Shuffleboard.enableActuatorWidgets();
testInit();
m_watchdog.addEpoch("testInit()");
m_lastMode = Mode.kTest;
@@ -255,6 +260,7 @@ public abstract class IterativeRobotBase extends RobotBase {
SmartDashboard.updateValues();
LiveWindow.updateValues();
Shuffleboard.update();
// Warn on loop time overruns
if (m_watchdog.isExpired()) {

View File

@@ -23,6 +23,7 @@ import edu.wpi.first.hal.HAL;
import edu.wpi.first.hal.HALUtil;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.wpilibj.livewindow.LiveWindow;
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
import edu.wpi.first.wpilibj.util.WPILibVersion;
/**
@@ -91,6 +92,7 @@ public abstract class RobotBase implements AutoCloseable {
inst.getTable("LiveWindow").getSubTable(".status").getEntry("LW Enabled").setBoolean(false);
LiveWindow.setEnabled(false);
Shuffleboard.disableActuatorWidgets();
}
@Deprecated

View File

@@ -11,6 +11,7 @@ import edu.wpi.first.hal.FRCNetComm.tInstances;
import edu.wpi.first.hal.FRCNetComm.tResourceType;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.wpilibj.livewindow.LiveWindow;
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
/**
* A simple robot base class that knows the standard FRC competition states (disabled, autonomous,
@@ -142,6 +143,7 @@ public class SampleRobot extends RobotBase {
}
} else if (isTest()) {
LiveWindow.setEnabled(true);
Shuffleboard.enableActuatorWidgets();
m_ds.InTest(true);
test();
m_ds.InTest(false);
@@ -149,6 +151,7 @@ public class SampleRobot extends RobotBase {
Timer.delay(0.01);
}
LiveWindow.setEnabled(false);
Shuffleboard.disableActuatorWidgets();
} else {
m_ds.InOperatorControl(true);
operatorControl();

View File

@@ -0,0 +1,62 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj.shuffleboard;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.wpilibj.Sendable;
import edu.wpi.first.wpilibj.smartdashboard.SendableBuilder;
import edu.wpi.first.wpilibj.smartdashboard.SendableBuilderImpl;
/**
* A Shuffleboard widget that handles a {@link Sendable} object such as a speed controller or
* sensor.
*/
public final class ComplexWidget extends ShuffleboardWidget<ComplexWidget> {
private final Sendable m_sendable;
private SendableBuilderImpl m_builder;
ComplexWidget(ShuffleboardContainer parent, String title, Sendable sendable) {
super(parent, title);
m_sendable = sendable;
}
@Override
public void buildInto(NetworkTable parentTable, NetworkTable metaTable) {
buildMetadata(metaTable);
if (m_builder == null) {
m_builder = new SendableBuilderImpl();
m_builder.setTable(parentTable.getSubTable(getTitle()));
m_sendable.initSendable(m_builder);
m_builder.startListeners();
}
m_builder.updateTable();
}
/**
* Enables user control of this widget in the Shuffleboard application. This method is
* package-private to prevent users from enabling control themselves. Has no effect if the
* sendable is not marked as an actuator with {@link SendableBuilder#setActuator}.
*/
void enableIfActuator() {
if (m_builder.isActuator()) {
m_builder.startLiveWindowMode();
}
}
/**
* Disables user control of this widget in the Shuffleboard application. This method is
* package-private to prevent users from enabling control themselves. Has no effect if the
* sendable is not marked as an actuator with {@link SendableBuilder#setActuator}.
*/
void disableIfActuator() {
if (m_builder.isActuator()) {
m_builder.stopLiveWindowMode();
}
}
}

View File

@@ -0,0 +1,87 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj.shuffleboard;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.wpilibj.Sendable;
/**
* A helper class for Shuffleboard containers to handle common child operations.
*/
final class ContainerHelper {
private final ShuffleboardContainer m_container;
private final Set<String> m_usedTitles = new HashSet<>();
private final List<ShuffleboardComponent<?>> m_components = new ArrayList<>();
private final Map<String, ShuffleboardLayout> m_layouts = new LinkedHashMap<>();
ContainerHelper(ShuffleboardContainer container) {
m_container = container;
}
List<ShuffleboardComponent<?>> getComponents() {
return m_components;
}
ShuffleboardLayout getLayout(String type, String title) {
if (!m_layouts.containsKey(title)) {
ShuffleboardLayout layout = new ShuffleboardLayout(m_container, type, title);
m_components.add(layout);
m_layouts.put(title, layout);
}
return m_layouts.get(title);
}
ComplexWidget add(String title, Sendable sendable) {
checkTitle(title);
ComplexWidget widget = new ComplexWidget(m_container, title, sendable);
m_components.add(widget);
return widget;
}
ComplexWidget add(Sendable sendable) throws IllegalArgumentException {
if (sendable.getName() == null || sendable.getName().isEmpty()) {
throw new IllegalArgumentException("Sendable must have a name");
}
return add(sendable.getName(), sendable);
}
SimpleWidget add(String title, Object defaultValue) {
Objects.requireNonNull(title, "Title cannot be null");
Objects.requireNonNull(defaultValue, "Default value cannot be null");
checkTitle(title);
checkNtType(defaultValue);
SimpleWidget widget = new SimpleWidget(m_container, title);
m_components.add(widget);
widget.getEntry().setDefaultValue(defaultValue);
return widget;
}
private static void checkNtType(Object data) {
if (!NetworkTableEntry.isValidDataType(data)) {
throw new IllegalArgumentException(
"Cannot add data of type " + data.getClass().getName() + " to Shuffleboard");
}
}
private void checkTitle(String title) {
if (m_usedTitles.contains(title)) {
throw new IllegalArgumentException("Title is already in use: " + title);
}
m_usedTitles.add(title);
}
}

View File

@@ -0,0 +1,104 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj.shuffleboard;
import edu.wpi.first.networktables.NetworkTableInstance;
/**
* The Shuffleboard class provides a mechanism with which data can be added and laid out in the
* Shuffleboard dashboard application from a robot program. Tabs and layouts can be specified, as
* well as choosing which widgets to display with and setting properties of these widgets; for
* example, programmers can specify a specific {@code boolean} value to be displayed with a toggle
* button instead of the default colored box, or set custom colors for that box.
*
* <p>For example, displaying a boolean entry with a toggle button:
* <pre>{@code
* NetworkTableEntry myBoolean = Shuffleboard.getTab("Example Tab")
* .add("My Boolean", false)
* .withWidget("Toggle Button")
* .getEntry();
* }</pre>
*
* Changing the colors of the boolean box:
* <pre>{@code
* NetworkTableEntry myBoolean = Shuffleboard.getTab("Example Tab")
* .add("My Boolean", false)
* .withWidget("Boolean Box")
* .withProperties(Map.of("colorWhenTrue", "green", "colorWhenFalse", "maroon"))
* .getEntry();
* }</pre>
*
* Specifying a parent layout. Note that the layout type must <i>always</i> be specified, even if
* the layout has already been generated by a previously defined entry.
* <pre>{@code
* NetworkTableEntry myBoolean = Shuffleboard.getTab("Example Tab")
* .getLayout("List", "Example List")
* .add("My Boolean", false)
* .withWidget("Toggle Button")
* .getEntry();
* }</pre>
* </p>
*
* <p>Teams are encouraged to set up shuffleboard layouts at the start of the robot program.</p>
*/
public final class Shuffleboard {
/**
* The name of the base NetworkTable into which all Shuffleboard data will be added.
*/
public static final String kBaseTableName = "/Shuffleboard";
private static final ShuffleboardRoot root =
new ShuffleboardInstance(NetworkTableInstance.getDefault());
// TODO usage reporting
private Shuffleboard() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
/**
* Updates all the values in Shuffleboard. Iterative and timed robots are pre-configured to call
* this method in the main robot loop; teams using custom robot base classes, or subclass
* SampleRobot, should make sure to call this repeatedly to keep data on the dashboard up to date.
*/
public static void update() {
root.update();
}
/**
* Gets the Shuffleboard tab with the given title, creating it if it does not already exist.
*
* @param title the title of the tab
* @return the tab with the given title
*/
public static ShuffleboardTab getTab(String title) {
return root.getTab(title);
}
/**
* Enables user control of widgets containing actuators: speed controllers, relays, etc. This
* should only be used when the robot is in test mode. IterativeRobotBase and SampleRobot are
* both configured to call this method when entering test mode; most users should not need to use
* this method directly.
*/
public static void enableActuatorWidgets() {
root.enableActuatorWidgets();
}
/**
* Disables user control of widgets containing actuators. For safety reasons, actuators should
* only be controlled while in test mode. IterativeRobotBase and SampleRobot are both configured
* to call this method when exiting in test mode; most users should not need to use
* this method directly.
*/
public static void disableActuatorWidgets() {
update(); // Need to update to make sure the sendable builders are initialized
root.disableActuatorWidgets();
}
}

View File

@@ -0,0 +1,147 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj.shuffleboard;
import java.util.Map;
import java.util.Objects;
import edu.wpi.first.networktables.NetworkTable;
/**
* A generic component in Shuffleboard.
*
* @param <C> the self type
*/
public abstract class ShuffleboardComponent<C extends ShuffleboardComponent<C>>
implements ShuffleboardValue {
private final ShuffleboardContainer m_parent;
private final String m_title;
private String m_type;
private Map<String, Object> m_properties;
private boolean m_metadataDirty = true;
private int m_column = -1;
private int m_row = -1;
private int m_width = -1;
private int m_height = -1;
protected ShuffleboardComponent(ShuffleboardContainer parent, String title, String type) {
m_parent = Objects.requireNonNull(parent, "Parent cannot be null");
m_title = Objects.requireNonNull(title, "Title cannot be null");
m_type = type;
}
protected ShuffleboardComponent(ShuffleboardContainer parent, String title) {
this(parent, title, null);
}
public final ShuffleboardContainer getParent() {
return m_parent;
}
protected final void setType(String type) {
m_type = type;
m_metadataDirty = true;
}
public final String getType() {
return m_type;
}
@Override
public final String getTitle() {
return m_title;
}
/**
* Gets the custom properties for this component. May be null.
*/
final Map<String, Object> getProperties() {
return m_properties;
}
/**
* Sets custom properties for this component. Property names are case- and whitespace-insensitive
* (capitalization and spaces do not matter).
*
* @param properties the properties for this component
* @return this component
*/
public final C withProperties(Map<String, Object> properties) {
m_properties = properties;
m_metadataDirty = true;
return (C) this;
}
/**
* Sets the position of this component in the tab. This has no effect if this component is inside
* a layout.
*
* <p>If the position of a single component is set, it is recommended to set the positions of
* <i>all</i> components inside a tab to prevent Shuffleboard from automatically placing another
* component there before the one with the specific position is sent.
*
* @param columnIndex the column in the tab to place this component
* @param rowIndex the row in the tab to place this component
* @return this component
*/
public final C withPosition(int columnIndex, int rowIndex) {
m_column = columnIndex;
m_row = rowIndex;
m_metadataDirty = true;
return (C) this;
}
/**
* Sets the size of this component in the tab. This has no effect if this component is inside a
* layout.
*
* @param width how many columns wide the component should be
* @param height how many rows high the component should be
* @return this component
*/
public final C withSize(int width, int height) {
m_width = width;
m_height = height;
m_metadataDirty = true;
return (C) this;
}
protected final void buildMetadata(NetworkTable metaTable) {
if (!m_metadataDirty) {
return;
}
// Component type
if (getType() == null) {
metaTable.getEntry("PreferredComponent").delete();
} else {
metaTable.getEntry("PreferredComponent").forceSetString(getType());
}
// Tile size
if (m_width <= 0 || m_height <= 0) {
metaTable.getEntry("Size").delete();
} else {
metaTable.getEntry("Size").setDoubleArray(new double[]{m_width, m_height});
}
// Tile position
if (m_column < 0 || m_row < 0) {
metaTable.getEntry("Position").delete();
} else {
metaTable.getEntry("Position").setDoubleArray(new double[]{m_column, m_row});
}
// Custom properties
if (getProperties() != null) {
NetworkTable propTable = metaTable.getSubTable("Properties");
getProperties().forEach((name, value) -> propTable.getEntry(name).setValue(value));
}
m_metadataDirty = false;
}
}

View File

@@ -0,0 +1,86 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj.shuffleboard;
import java.util.List;
import edu.wpi.first.wpilibj.Sendable;
/**
* Common interface for objects that can contain shuffleboard components.
*/
public interface ShuffleboardContainer extends ShuffleboardValue {
/**
* Gets the components that are direct children of this container.
*/
List<ShuffleboardComponent<?>> getComponents();
/**
* Gets the layout with the given type and title, creating it if it does not already exist at the
* time this method is called.
*
* @param type the type of the layout, eg "List" or "Grid"
* @param title the title of the layout
* @return the layout
*/
ShuffleboardLayout getLayout(String type, String title);
/**
* Adds a widget to this container to display the given sendable.
*
* @param title the title of the widget
* @param sendable the sendable to display
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
ComplexWidget add(String title, Sendable sendable) throws IllegalArgumentException;
/**
* Adds a widget to this container to display the given sendable.
*
* @param sendable the sendable to display
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title, or if the sendable's name has not been specified
*/
ComplexWidget add(Sendable sendable);
/**
* Adds a widget to this container to display the given data.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
* @see #addPersistent(String, Object) add(String title, Object defaultValue)
*/
SimpleWidget add(String title, Object defaultValue) throws IllegalArgumentException;
/**
* Adds a widget to this container to display a simple piece of data. Unlike
* {@link #add(String, Object)}, the value in the widget will be saved on the robot and will be
* used when the robot program next starts rather than {@code defaultValue}.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
* @see #add(String, Object) add(String title, Object defaultValue)
*/
default SimpleWidget addPersistent(String title, Object defaultValue)
throws IllegalArgumentException {
SimpleWidget widget = add(title, defaultValue);
widget.getEntry().setPersistent();
return widget;
}
}

View File

@@ -0,0 +1,93 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj.shuffleboard;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
final class ShuffleboardInstance implements ShuffleboardRoot {
private final Map<String, ShuffleboardTab> m_tabs = new LinkedHashMap<>();
private boolean m_tabsChanged = false; // NOPMD redundant field initializer
private final NetworkTable m_rootTable;
private final NetworkTable m_rootMetaTable;
ShuffleboardInstance(NetworkTableInstance ntInstance) {
Objects.requireNonNull(ntInstance, "NetworkTable instance cannot be null");
m_rootTable = ntInstance.getTable(Shuffleboard.kBaseTableName);
m_rootMetaTable = m_rootTable.getSubTable(".metadata");
}
@Override
public ShuffleboardTab getTab(String title) {
Objects.requireNonNull(title, "Tab title cannot be null");
if (!m_tabs.containsKey(title)) {
m_tabs.put(title, new ShuffleboardTab(this, title));
m_tabsChanged = true;
}
return m_tabs.get(title);
}
@Override
public void update() {
if (m_tabsChanged) {
String[] tabTitles = m_tabs.values()
.stream()
.map(ShuffleboardTab::getTitle)
.toArray(String[]::new);
m_rootMetaTable.getEntry("Tabs").forceSetStringArray(tabTitles);
m_tabsChanged = false;
}
for (ShuffleboardTab tab : m_tabs.values()) {
String title = tab.getTitle();
tab.buildInto(m_rootTable, m_rootMetaTable.getSubTable(title));
}
}
@Override
public void enableActuatorWidgets() {
applyToAllComplexWidgets(ComplexWidget::enableIfActuator);
}
@Override
public void disableActuatorWidgets() {
applyToAllComplexWidgets(ComplexWidget::disableIfActuator);
}
/**
* Applies the function {@code func} to all complex widgets in this root, regardless of how they
* are nested.
*
* @param func the function to apply to all complex widgets
*/
private void applyToAllComplexWidgets(Consumer<ComplexWidget> func) {
for (ShuffleboardTab tab : m_tabs.values()) {
apply(tab, func);
}
}
/**
* Applies the function {@code func} to all complex widgets in {@code container}. Helper method
* for {@link #applyToAllComplexWidgets}.
*/
private void apply(ShuffleboardContainer container, Consumer<ComplexWidget> func) {
for (ShuffleboardComponent<?> component : container.getComponents()) {
if (component instanceof ComplexWidget) {
func.accept((ComplexWidget) component);
}
if (component instanceof ShuffleboardContainer) {
apply((ShuffleboardContainer) component, func);
}
}
}
}

View File

@@ -0,0 +1,62 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj.shuffleboard;
import java.util.List;
import java.util.Objects;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.wpilibj.Sendable;
/**
* A layout in a Shuffleboard tab. Layouts can contain widgets and other layouts.
*/
public class ShuffleboardLayout extends ShuffleboardComponent<ShuffleboardLayout>
implements ShuffleboardContainer {
private final ContainerHelper m_helper = new ContainerHelper(this);
ShuffleboardLayout(ShuffleboardContainer parent, String name, String type) {
super(parent, Objects.requireNonNull(type, "Layout type must be specified"), name);
}
@Override
public List<ShuffleboardComponent<?>> getComponents() {
return m_helper.getComponents();
}
@Override
public ShuffleboardLayout getLayout(String type, String title) {
return m_helper.getLayout(type, title);
}
@Override
public ComplexWidget add(String title, Sendable sendable) throws IllegalArgumentException {
return m_helper.add(title, sendable);
}
@Override
public ComplexWidget add(Sendable sendable) throws IllegalArgumentException {
return m_helper.add(sendable);
}
@Override
public SimpleWidget add(String title, Object defaultValue) throws IllegalArgumentException {
return m_helper.add(title, defaultValue);
}
@Override
public void buildInto(NetworkTable parentTable, NetworkTable metaTable) {
buildMetadata(metaTable);
NetworkTable table = parentTable.getSubTable(getTitle());
table.getEntry(".type").setString("ShuffleboardLayout");
for (ShuffleboardComponent<?> component : getComponents()) {
component.buildInto(table, metaTable.getSubTable(component.getTitle()));
}
}
}

View File

@@ -0,0 +1,41 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj.shuffleboard;
/**
* The root of the data placed in Shuffleboard. It contains the tabs, but no data is placed
* directly in the root.
*
* <p>This class is package-private to minimize API surface area.
*/
interface ShuffleboardRoot {
/**
* Gets the tab with the given title, creating it if it does not already exist.
*
* @param title the title of the tab
* @return the tab with the given title
*/
ShuffleboardTab getTab(String title);
/**
* Updates all tabs.
*/
void update();
/**
* Enables all widgets in Shuffleboard that offer user control over actuators.
*/
void enableActuatorWidgets();
/**
* Disables all widgets in Shuffleboard that offer user control over actuators.
*/
void disableActuatorWidgets();
}

View File

@@ -0,0 +1,74 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj.shuffleboard;
import java.util.List;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.wpilibj.Sendable;
/**
* Represents a tab in the Shuffleboard dashboard. Widgets can be added to the tab with
* {@link #add(Sendable)}, {@link #add(String, Object)}, and {@link #add(String, Sendable)}. Widgets
* can also be added to layouts with {@link #getLayout(String, String)}; layouts can be nested
* arbitrarily deep (note that too many levels may make deeper components unusable).
*/
public final class ShuffleboardTab implements ShuffleboardContainer {
private final ContainerHelper m_helper = new ContainerHelper(this);
private final ShuffleboardRoot m_root;
private final String m_title;
ShuffleboardTab(ShuffleboardRoot root, String title) {
m_root = root;
m_title = title;
}
@Override
public String getTitle() {
return m_title;
}
ShuffleboardRoot getRoot() {
return m_root;
}
@Override
public List<ShuffleboardComponent<?>> getComponents() {
return m_helper.getComponents();
}
@Override
public ShuffleboardLayout getLayout(String type, String title) {
return m_helper.getLayout(type, title);
}
@Override
public ComplexWidget add(String title, Sendable sendable) {
return m_helper.add(title, sendable);
}
@Override
public ComplexWidget add(Sendable sendable) throws IllegalArgumentException {
return m_helper.add(sendable);
}
@Override
public SimpleWidget add(String title, Object defaultValue) {
return m_helper.add(title, defaultValue);
}
@Override
public void buildInto(NetworkTable parentTable, NetworkTable metaTable) {
NetworkTable tabTable = parentTable.getSubTable(m_title);
tabTable.getEntry(".type").setString("ShuffleboardTab");
for (ShuffleboardComponent<?> component : m_helper.getComponents()) {
component.buildInto(tabTable, metaTable.getSubTable(component.getTitle()));
}
}
}

View File

@@ -0,0 +1,31 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj.shuffleboard;
import edu.wpi.first.networktables.NetworkTable;
interface ShuffleboardValue {
/**
* Gets the title of this Shuffleboard value.
*/
String getTitle();
/**
* Builds the entries for this value.
*
* @param parentTable the table containing all the data for the parent. Values that require a
* complex entry or table structure should call {@code
* parentTable.getSubTable(getTitle())} to get the table to put data into.
* Values that only use a single entry should call {@code
* parentTable.getEntry(getTitle())} to get that entry.
* @param metaTable the table containing all the metadata for this value and its sub-values
*/
void buildInto(NetworkTable parentTable, NetworkTable metaTable);
}

View File

@@ -0,0 +1,36 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj.shuffleboard;
/**
* Abstract superclass for widgets.
*
* <p>This class is package-private to minimize API surface area.
*
* @param <W> the self type
*/
abstract class ShuffleboardWidget<W extends ShuffleboardWidget<W>>
extends ShuffleboardComponent<W> {
ShuffleboardWidget(ShuffleboardContainer parent, String title) {
super(parent, title);
}
/**
* Sets the type of widget used to display the data. If not set, the default widget type will be
* used.
*
* @param widgetType the type of the widget used to display the data
* @return this widget object
*/
public final W withWidget(String widgetType) {
setType(widgetType);
return (W) this;
}
}

View File

@@ -0,0 +1,50 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj.shuffleboard;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableEntry;
/**
* A Shuffleboard widget that handles a single data point such as a number or string.
*/
public final class SimpleWidget extends ShuffleboardWidget<SimpleWidget> {
private NetworkTableEntry m_entry;
SimpleWidget(ShuffleboardContainer parent, String title) {
super(parent, title);
}
/**
* Gets the NetworkTable entry that contains the data for this widget.
*/
public NetworkTableEntry getEntry() {
if (m_entry == null) {
forceGenerate();
}
return m_entry;
}
@Override
public void buildInto(NetworkTable parentTable, NetworkTable metaTable) {
buildMetadata(metaTable);
if (m_entry == null) {
m_entry = parentTable.getEntry(getTitle());
}
}
private void forceGenerate() {
ShuffleboardContainer parent = getParent();
while (parent instanceof ShuffleboardLayout) {
parent = ((ShuffleboardLayout) parent).getParent();
}
ShuffleboardTab tab = (ShuffleboardTab) parent;
tab.getRoot().update();
}
}