diff --git a/wpilibc/src/main/native/cpp/shuffleboard/BuiltInLayouts.cpp b/wpilibc/src/main/native/cpp/shuffleboard/BuiltInLayouts.cpp new file mode 100644 index 0000000000..5d09310318 --- /dev/null +++ b/wpilibc/src/main/native/cpp/shuffleboard/BuiltInLayouts.cpp @@ -0,0 +1,13 @@ +/*----------------------------------------------------------------------------*/ +/* 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. */ +/*----------------------------------------------------------------------------*/ + +#include "frc/shuffleboard/BuiltInLayouts.h" + +using namespace frc; + +const LayoutType BuiltInLayouts::kList{"List Layout"}; +const LayoutType BuiltInLayouts::kGrid{"Grid Layout"}; diff --git a/wpilibc/src/main/native/cpp/shuffleboard/BuiltInWidgets.cpp b/wpilibc/src/main/native/cpp/shuffleboard/BuiltInWidgets.cpp new file mode 100644 index 0000000000..df6a1f3334 --- /dev/null +++ b/wpilibc/src/main/native/cpp/shuffleboard/BuiltInWidgets.cpp @@ -0,0 +1,35 @@ +/*----------------------------------------------------------------------------*/ +/* 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. */ +/*----------------------------------------------------------------------------*/ + +#include "frc/shuffleboard/BuiltInWidgets.h" + +using namespace frc; + +const WidgetType BuiltInWidgets::kTextView{"Text View"}; +const WidgetType BuiltInWidgets::kNumberSlider{"Number Slider"}; +const WidgetType BuiltInWidgets::kNumberBar{"Number Bar"}; +const WidgetType BuiltInWidgets::kDial{"Simple Dial"}; +const WidgetType BuiltInWidgets::kGraph{"Graph"}; +const WidgetType BuiltInWidgets::kBooleanBox{"Boolean Box"}; +const WidgetType BuiltInWidgets::kToggleButton{"Toggle Button"}; +const WidgetType BuiltInWidgets::kToggleSwitch{"Toggle Switch"}; +const WidgetType BuiltInWidgets::kVoltageView{"Voltage View"}; +const WidgetType BuiltInWidgets::kPowerDistributionPanel{"PDP"}; +const WidgetType BuiltInWidgets::kComboBoxChooser{"ComboBox Chooser"}; +const WidgetType BuiltInWidgets::kSplitButtonChooser{"Split Button Chooser"}; +const WidgetType BuiltInWidgets::kEncoder{"Encoder"}; +const WidgetType BuiltInWidgets::kSpeedController{"Speed Controller"}; +const WidgetType BuiltInWidgets::kCommand{"Command"}; +const WidgetType BuiltInWidgets::kPIDCommand{"PID Command"}; +const WidgetType BuiltInWidgets::kPIDController{"PID Controller"}; +const WidgetType BuiltInWidgets::kAccelerometer{"Accelerometer"}; +const WidgetType BuiltInWidgets::k3AxisAccelerometer{"3-Axis Accelerometer"}; +const WidgetType BuiltInWidgets::kGyro{"Gyro"}; +const WidgetType BuiltInWidgets::kRelay{"Relay"}; +const WidgetType BuiltInWidgets::kDifferentialDrive{"Differential Drivebase"}; +const WidgetType BuiltInWidgets::kMecanumDrive{"Mecanum Drivebase"}; +const WidgetType BuiltInWidgets::kCameraStream{"Camera Stream"}; diff --git a/wpilibc/src/main/native/cpp/shuffleboard/LayoutType.cpp b/wpilibc/src/main/native/cpp/shuffleboard/LayoutType.cpp new file mode 100644 index 0000000000..a21cad9600 --- /dev/null +++ b/wpilibc/src/main/native/cpp/shuffleboard/LayoutType.cpp @@ -0,0 +1,12 @@ +/*----------------------------------------------------------------------------*/ +/* 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. */ +/*----------------------------------------------------------------------------*/ + +#include "frc/shuffleboard/LayoutType.h" + +using namespace frc; + +wpi::StringRef LayoutType::GetLayoutName() const { return m_layoutName; } diff --git a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp index 1183d4f34c..336a27f94b 100644 --- a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp +++ b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp @@ -25,8 +25,13 @@ ShuffleboardContainer::GetComponents() const { return m_components; } -ShuffleboardLayout& ShuffleboardContainer::GetLayout(const wpi::Twine& type, - const wpi::Twine& title) { +ShuffleboardLayout& ShuffleboardContainer::GetLayout(const wpi::Twine& title, + const LayoutType& type) { + return GetLayout(title, type.GetLayoutName()); +} + +ShuffleboardLayout& ShuffleboardContainer::GetLayout(const wpi::Twine& title, + const wpi::Twine& type) { wpi::SmallVector storage; auto titleRef = title.toStringRef(storage); if (m_layouts.count(titleRef) == 0) { @@ -38,6 +43,16 @@ ShuffleboardLayout& ShuffleboardContainer::GetLayout(const wpi::Twine& type, return *m_layouts[titleRef]; } +ShuffleboardLayout& ShuffleboardContainer::GetLayout(const wpi::Twine& title) { + wpi::SmallVector storage; + auto titleRef = title.toStringRef(storage); + if (m_layouts.count(titleRef) == 0) { + wpi_setWPIErrorWithContext( + InvalidParameter, "No layout with the given title has been defined"); + } + return *m_layouts[titleRef]; +} + ComplexWidget& ShuffleboardContainer::Add(const wpi::Twine& title, Sendable& sendable) { CheckTitle(title); diff --git a/wpilibc/src/main/native/cpp/shuffleboard/WidgetType.cpp b/wpilibc/src/main/native/cpp/shuffleboard/WidgetType.cpp new file mode 100644 index 0000000000..cb73d30cc5 --- /dev/null +++ b/wpilibc/src/main/native/cpp/shuffleboard/WidgetType.cpp @@ -0,0 +1,12 @@ +/*----------------------------------------------------------------------------*/ +/* 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. */ +/*----------------------------------------------------------------------------*/ + +#include "frc/shuffleboard/WidgetType.h" + +using namespace frc; + +wpi::StringRef WidgetType::GetWidgetName() const { return m_widgetName; } diff --git a/wpilibc/src/main/native/include/frc/WPIErrors.h b/wpilibc/src/main/native/include/frc/WPIErrors.h index bbebe3d247..a7c4f16725 100644 --- a/wpilibc/src/main/native/include/frc/WPIErrors.h +++ b/wpilibc/src/main/native/include/frc/WPIErrors.h @@ -75,6 +75,7 @@ S(SmartDashboardMissingKey, -43, "SmartDashboard data does not exist"); S(CommandIllegalUse, -50, "Illegal use of Command"); S(UnsupportedInSimulation, -80, "Unsupported in simulation"); S(CameraServerError, -90, "CameraServer error"); +S(InvalidParameter, -100, "Invalid parameter value"); // Warnings S(SampleRateTooHigh, 1, "Analog module sample rate is too high"); diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/BuiltInLayouts.h b/wpilibc/src/main/native/include/frc/shuffleboard/BuiltInLayouts.h new file mode 100644 index 0000000000..6a61b870ba --- /dev/null +++ b/wpilibc/src/main/native/include/frc/shuffleboard/BuiltInLayouts.h @@ -0,0 +1,53 @@ +/*----------------------------------------------------------------------------*/ +/* 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. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include "frc/shuffleboard/LayoutType.h" + +namespace frc { + +/** + * The types of layouts bundled with Shuffleboard. + * + *
{@code
+ * ShuffleboardLayout myList = Shuffleboard::GetTab("My Tab")
+ *   .GetLayout(BuiltinLayouts::kList, "My List");
+ * }
+ */ +class BuiltInLayouts { + public: + /** + * Groups components in a vertical list. New widgets added to the layout will + * be placed at the bottom of the list.
Custom properties: + * + * + * + *
NameTypeDefault ValueNotes
Label positionString"BOTTOM"The position of component labels inside the grid. One of + * {@code ["TOP", "LEFT", "BOTTOM", "RIGHT", "HIDDEN"}
+ */ + static const LayoutType kList; + + /** + * Groups components in an n x m grid. Grid layouts default to + * 3x3.
Custom properties: + * + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
Number of columnsNumber3Must be in the + * range [1,15]
Number of rowsNumber3Must be in the + * range [1,15]
Label position String"BOTTOM"The position of component labels inside the grid. + * One of {@code ["TOP", "LEFT", "BOTTOM", "RIGHT", "HIDDEN"}
+ */ + static const LayoutType kGrid; +}; + +} // namespace frc diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/BuiltInWidgets.h b/wpilibc/src/main/native/include/frc/shuffleboard/BuiltInWidgets.h new file mode 100644 index 0000000000..1fd720fbda --- /dev/null +++ b/wpilibc/src/main/native/include/frc/shuffleboard/BuiltInWidgets.h @@ -0,0 +1,387 @@ +/*----------------------------------------------------------------------------*/ +/* 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. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include "frc/shuffleboard/WidgetType.h" + +namespace frc { + +/** + * The types of the widgets bundled with Shuffleboard. + * + *

For example, setting a number to be displayed with a slider: + *

{@code
+ * NetworkTableEntry example = Shuffleboard.getTab("My Tab")
+ *   .add("My Number", 0)
+ *   .withWidget(BuiltInWidgets.kNumberSlider)
+ *   .getEntry();
+ * }
+ * + *

Each value in this enum goes into detail on what data types that widget + * can support, as well as the custom properties that widget uses. + */ +class BuiltInWidgets { + public: + /** + * Displays a value with a simple text field. + *
Supported types: + *

+ *
This widget has no custom properties. + */ + static const WidgetType kTextView; + /** + * Displays a number with a controllable slider. + *
Supported types: + * + *
Custom properties: + * + * + * + *
NameTypeDefault ValueNotes
MinNumber-1.0The minimum value of the + * slider
MaxNumber1.0The maximum + * value of the slider
Block + * incrementNumber0.0625 How much to move the + * slider by with the arrow keys
+ */ + static const WidgetType kNumberSlider; + /** + * Displays a number with a view-only bar. + *
Supported types: + * + *
Custom properties: + * + * + * + * + *
NameTypeDefault ValueNotes
MinNumber-1.0The minimum value of the + * bar
MaxNumber1.0The maximum + * value of the bar
CenterNumber0The center ("zero") value + * of the bar
+ */ + static const WidgetType kNumberBar; + /** + * Displays a number with a view-only dial. Displayed values are rounded to + * the nearest integer.
Supported types: + *
Custom properties: + * + * + * + *
NameTypeDefault ValueNotes
MinNumber0The minimum value of the + * dial
MaxNumber100The maximum + * value of the dial
Show + * valueBooleantrue Whether or not to show the + * value as text
+ */ + static const WidgetType kDial; + /** + * Displays a number with a graph. NOTE: graphs can be taxing + * on the computer running the dashboard. Keep the number of visible data + * points to a minimum. Making the widget smaller also helps with performance, + * but may cause the graph to become difficult to read.
Supported types: + * + *
Custom properties: + * + * + * + * + *
NameTypeDefault ValueNotes
Visible timeNumber30How long, in seconds, should past data be visible for
+ */ + static const WidgetType kGraph; + /** + * Displays a boolean value as a large colored box. + *
Supported types: + * + *
Custom properties: + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
Color when trueColor"green"Can be specified as a string ({@code "#00FF00"}) or a rgba integer + * ({@code 0x00FF0000}) + *
Color when falseColor"red"Can be specified as a string or a number
+ */ + static const WidgetType kBooleanBox; + /** + * Displays a boolean with a large interactive toggle button. + *
Supported types: + * + *
This widget has no custom properties. + */ + static const WidgetType kToggleButton; + /** + * Displays a boolean with a fixed-size toggle switch. + *
Supported types: + * + *
This widget has no custom properties. + */ + static const WidgetType kToggleSwitch; + /** + * Displays an analog input or a raw number with a number bar. + *
Supported types: + * + *
Custom properties: + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
MinNumber0The minimum value of the + * bar
MaxNumber5The maximum + * value of the bar
CenterNumber0The center ("zero") value + * of the bar
OrientationString"HORIZONTAL"The orientation of the bar. One of {@code ["HORIZONTAL", + * "VERTICAL"]}
Number of tick + * marksNumber5 The number of discrete ticks on the + * bar
+ */ + static const WidgetType kVoltageView; + /** + * Displays a {@link edu.wpi.first.wpilibj.PowerDistributionPanel + * PowerDistributionPanel}.
Supported types: + *
Custom properties: + * + * + * + * + *
NameTypeDefault ValueNotes
Show voltage and current valuesBooleantrueWhether or not to display the voltage and current draw
+ */ + static const WidgetType kPowerDistributionPanel; + /** + * Displays a {@link edu.wpi.first.wpilibj.smartdashboard.SendableChooser + * SendableChooser} with a dropdown combo box with a list of options. + *
Supported types: + * + *
This widget has no custom properties. + */ + static const WidgetType kComboBoxChooser; + /** + * Displays a {@link edu.wpi.first.wpilibj.smartdashboard.SendableChooser + * SendableChooser} with a toggle button for each available option. + *
Supported types: + * + *
This widget has no custom properties. + */ + static const WidgetType kSplitButtonChooser; + /** + * Displays an {@link edu.wpi.first.wpilibj.Encoder} displaying its speed, + * total travelled distance, and its distance per tick.
Supported types: + * + *
This widget has no custom properties. + */ + static const WidgetType kEncoder; + /** + * Displays a {@link edu.wpi.first.wpilibj.SpeedController SpeedController}. + * The speed controller will be controllable from the dashboard when test mode + * is enabled, but will otherwise be view-only.
Supported types: + *
Custom properties: + * + * + * + * + *
NameTypeDefault ValueNotes
OrientationString"HORIZONTAL"One of {@code ["HORIZONTAL", "VERTICAL"]}
+ */ + static const WidgetType kSpeedController; + /** + * Displays a command with a toggle button. Pressing the button will start the + * command, and the button will automatically release when the command + * completes.
Supported types: + *
This widget has no custom properties. + */ + static const WidgetType kCommand; + /** + * Displays a PID command with a checkbox and an editor for the PIDF + * constants. Selecting the checkbox will start the command, and the checkbox + * will automatically deselect when the command completes.
Supported + * types: + *
This widget has no custom properties. + */ + static const WidgetType kPIDCommand; + /** + * Displays a PID controller with an editor for the PIDF constants and a + * toggle switch for enabling and disabling the controller.
Supported + * types: + *
This widget has no custom properties. + */ + static const WidgetType kPIDController; + /** + * Displays an accelerometer with a number bar displaying the magnitude of the + * acceleration and text displaying the exact value.
Supported types: + *
Custom properties: + * + * + * + * + * + * + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
MinNumber-1The minimum acceleration value to display
MaxNumber1The maximum acceleration value to display
Show textBooleantrueShow or hide the acceleration values
PrecisionNumber2How many numbers to display after the decimal point
Show tick marksBooleanfalseShow or hide the tick marks on the number bars
+ */ + static const WidgetType kAccelerometer; + /** + * Displays a 3-axis accelerometer with a number bar for each axis' + * accleration.
Supported types: + *
Custom properties: + * + * + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
Range{@link Range}k16GThe accelerometer + * range
Show valueBooleantrueShow or hide the acceleration values
PrecisionNumber2How many numbers to display after the decimal point
Show tick marksBooleanfalseShow or hide the tick marks on the number bars
+ */ + static const WidgetType k3AxisAccelerometer; + /** + * Displays a gyro with a dial from 0 to 360 degrees. + *
Supported types: + * + *
Custom properties: + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
Major tick + * spacingNumber45Degrees
Starting angleNumber180How far to rotate the entire dial, in degrees
Show tick mark ringBooleantrue
+ */ + static const WidgetType kGyro; + /** + * Displays a relay with toggle buttons for each supported mode (off, on, + * forward, reverse).
Supported types: + *
This widget has no custom properties. + */ + static const WidgetType kRelay; + /** + * Displays a differential drive with a widget that displays the speed of each + * side of the drivebase and a vector for the direction and rotation of the + * drivebase. The widget will be controllable if the robot is in test mode. + *
Supported types: + * + *
Custom properties: + * + * + * + * + * + *
NameTypeDefault ValueNotes
Number of wheelsNumber4Must be a + * positive even integer + *
Wheel diameterNumber80Pixels
Show velocity vectorsBooleantrue
+ */ + static const WidgetType kDifferentialDrive; + /** + * Displays a mecanum drive with a widget that displays the speed of each + * wheel, and vectors for the direction and rotation of the drivebase. The + * widget will be controllable if the robot is in test mode.
Supported + * types: + *
Custom properties: + * + * + * + *
NameTypeDefault ValueNotes
Show velocity vectorsBooleantrue
+ */ + static const WidgetType kMecanumDrive; + /** + * Displays a camera stream. + *
Supported types: + * + *
Custom properties: + * + * + * + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
Show crosshairBooleantrueShow or hide a crosshair on the image
Crosshair colorColor"white"Can be a string or a rgba integer
Show controlsBooleantrueShow or hide the + * stream controls + *
RotationString"NONE"Rotates the displayed image. One of {@code ["NONE", "QUARTER_CW", + * "QUARTER_CCW", "HALF"]} + *
+ */ + static const WidgetType kCameraStream; +}; + +} // namespace frc diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/LayoutType.h b/wpilibc/src/main/native/include/frc/shuffleboard/LayoutType.h new file mode 100644 index 0000000000..22fe361704 --- /dev/null +++ b/wpilibc/src/main/native/include/frc/shuffleboard/LayoutType.h @@ -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. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include + +namespace frc { + +/** + * Represents the type of a layout in Shuffleboard. Using this is preferred over + * specifying raw strings, to avoid typos and having to know or look up the + * exact string name for a desired layout. + * + * @see BuiltInLayouts the built-in layout types + */ +class LayoutType { + public: + explicit LayoutType(const char* layoutName) : m_layoutName(layoutName) {} + ~LayoutType() = default; + + /** + * Gets the string type of the layout as defined by that layout in + * Shuffleboard. + */ + wpi::StringRef GetLayoutName() const; + + private: + wpi::StringRef m_layoutName; +}; + +} // namespace frc diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h index 2f6476589d..5098fdb398 100644 --- a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h +++ b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h @@ -18,6 +18,9 @@ #include #include +#include "frc/ErrorBase.h" +#include "frc/WPIErrors.h" +#include "frc/shuffleboard/LayoutType.h" #include "frc/shuffleboard/ShuffleboardComponentBase.h" #include "frc/shuffleboard/ShuffleboardValue.h" @@ -31,7 +34,8 @@ class SimpleWidget; /** * Common interface for objects that can contain shuffleboard components. */ -class ShuffleboardContainer : public virtual ShuffleboardValue { +class ShuffleboardContainer : public virtual ShuffleboardValue, + public ErrorBase { public: explicit ShuffleboardContainer(const wpi::Twine& title); @@ -49,12 +53,45 @@ class ShuffleboardContainer : public virtual ShuffleboardValue { * 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 + * @param title the title of the layout + * @param layoutType the type of the layout, eg "List" or "Grid" * @return the layout */ - ShuffleboardLayout& GetLayout(const wpi::Twine& type, - const wpi::Twine& title); + ShuffleboardLayout& GetLayout(const wpi::Twine& title, + const LayoutType& type); + + /** + * Gets the layout with the given type and title, creating it if it does not + * already exist at the time this method is called. Note: this method should + * only be used to use a layout type that is not already built into + * Shuffleboard. To use a layout built into Shuffleboard, use + * {@link #GetLayout(String, LayoutType)} and the layouts in {@link + * BuiltInLayouts}. + * + * @param title the title of the layout + * @param type the type of the layout, eg "List Layout" or "Grid Layout" + * @return the layout + * @see #GetLayout(String, LayoutType) + */ + ShuffleboardLayout& GetLayout(const wpi::Twine& title, + const wpi::Twine& type); + + /** + * Gets the already-defined layout in this container with the given title. + * + *
{@code
+   * Shuffleboard::GetTab("Example Tab")->getLayout("My Layout",
+   * &BuiltInLayouts.kList);
+   *
+   * // Later...
+   * Shuffleboard::GetTab("Example Tab")->GetLayout("My Layout");
+   * }
+ * + * @param title the title of the layout to get + * @return the layout with the given title + * @throws if no layout has yet been defined with the given title + */ + ShuffleboardLayout& GetLayout(const wpi::Twine& title); /** * Adds a widget to this container to display the given sendable. diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardWidget.h b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardWidget.h index 6ec520bf8a..2e67160d3f 100644 --- a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardWidget.h +++ b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardWidget.h @@ -10,6 +10,7 @@ #include #include "frc/shuffleboard/ShuffleboardComponent.h" +#include "frc/shuffleboard/WidgetType.h" namespace frc { @@ -36,6 +37,22 @@ class ShuffleboardWidget * * @param widgetType the type of the widget used to display the data * @return this widget object + * @see BuiltInWidgets + */ + Derived& withWidget(const WidgetType& widgetType) { + return WithWidget(widgetType.GetWidgetName()); + } + + /** + * Sets the type of widget used to display the data. If not set, the default + * widget type will be used. This method should only be used to use a widget + * that does not come built into Shuffleboard (i.e. one that comes with a + * custom or thrid-party plugin). To use a widget that is built into + * Shuffleboard, use {@link #withWidget(WidgetType)} and {@link + * BuiltInWidgets}. + * + * @param widgetType the type of the widget used to display the data + * @return this widget object */ Derived& WithWidget(const wpi::Twine& widgetType) { this->SetType(widgetType); diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/WidgetType.h b/wpilibc/src/main/native/include/frc/shuffleboard/WidgetType.h new file mode 100644 index 0000000000..f96e9b60e4 --- /dev/null +++ b/wpilibc/src/main/native/include/frc/shuffleboard/WidgetType.h @@ -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. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include + +namespace frc { + +/** + * Represents the type of a widget in Shuffleboard. Using this is preferred over + * specifying raw strings, to avoid typos and having to know or look up the + * exact string name for a desired widget. + * + * @see BuiltInWidgets the built-in widget types + */ +class WidgetType { + public: + explicit WidgetType(const char* widgetName) : m_widgetName(widgetName) {} + ~WidgetType() = default; + + /** + * Gets the string type of the widget as defined by that widget in + * Shuffleboard. + */ + wpi::StringRef GetWidgetName() const; + + private: + wpi::StringRef m_widgetName; +}; + +} // namespace frc diff --git a/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp b/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp index 6e0303bc17..ae21526444 100644 --- a/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp +++ b/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp @@ -33,7 +33,7 @@ class ShuffleboardInstanceTest : public testing::Test { TEST_F(ShuffleboardInstanceTest, PathFluent) { auto entry = m_shuffleboardInstance->GetTab("Tab Title") - .GetLayout("List", "List Layout") + .GetLayout("List Layout", "List") .Add("Data", "string") .WithWidget("Text View") .GetEntry(); @@ -45,10 +45,10 @@ TEST_F(ShuffleboardInstanceTest, PathFluent) { TEST_F(ShuffleboardInstanceTest, NestedLayoutsFluent) { auto entry = m_shuffleboardInstance->GetTab("Tab") - .GetLayout("List", "First") - .GetLayout("List", "Second") - .GetLayout("List", "Third") - .GetLayout("List", "Fourth") + .GetLayout("First", "List") + .GetLayout("Second", "List") + .GetLayout("Third", "List") + .GetLayout("Fourth", "List") .Add("Value", "string") .GetEntry(); @@ -60,10 +60,10 @@ TEST_F(ShuffleboardInstanceTest, NestedLayoutsFluent) { TEST_F(ShuffleboardInstanceTest, NestedLayoutsOop) { ShuffleboardTab& tab = m_shuffleboardInstance->GetTab("Tab"); - ShuffleboardLayout& first = tab.GetLayout("List", "First"); - ShuffleboardLayout& second = first.GetLayout("List", "Second"); - ShuffleboardLayout& third = second.GetLayout("List", "Third"); - ShuffleboardLayout& fourth = third.GetLayout("List", "Fourth"); + ShuffleboardLayout& first = tab.GetLayout("First", "List"); + ShuffleboardLayout& second = first.GetLayout("Second", "List"); + ShuffleboardLayout& third = second.GetLayout("Third", "List"); + ShuffleboardLayout& fourth = third.GetLayout("Fourth", "List"); SimpleWidget& widget = fourth.Add("Value", "string"); auto entry = widget.GetEntry(); @@ -75,17 +75,17 @@ TEST_F(ShuffleboardInstanceTest, NestedLayoutsOop) { TEST_F(ShuffleboardInstanceTest, LayoutTypeIsSet) { std::string layoutType = "Type"; - m_shuffleboardInstance->GetTab("Tab").GetLayout(layoutType, "Title"); + m_shuffleboardInstance->GetTab("Tab").GetLayout("Title", layoutType); m_shuffleboardInstance->Update(); nt::NetworkTableEntry entry = m_ntInstance.GetEntry( "/Shuffleboard/.metadata/Tab/Title/PreferredComponent"); EXPECT_EQ(layoutType, entry.GetString("Not Set")) << "Layout type not set"; } -TEST_F(ShuffleboardInstanceTest, NestedActuatoWidgetsAreDisabled) { +TEST_F(ShuffleboardInstanceTest, NestedActuatorWidgetsAreDisabled) { MockActuatorSendable sendable("Actuator"); m_shuffleboardInstance->GetTab("Tab") - .GetLayout("Layout", "Title") + .GetLayout("Title", "Type") .Add(sendable); auto controllableEntry = m_ntInstance.GetEntry("/Shuffleboard/Tab/Title/Actuator/.controllable"); diff --git a/wpilibcIntegrationTests/src/main/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp b/wpilibcIntegrationTests/src/main/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp index 6e0303bc17..d06f51032d 100644 --- a/wpilibcIntegrationTests/src/main/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp +++ b/wpilibcIntegrationTests/src/main/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp @@ -39,16 +39,16 @@ TEST_F(ShuffleboardInstanceTest, PathFluent) { .GetEntry(); EXPECT_EQ("string", entry.GetString("")) << "Wrong entry value"; - EXPECT_EQ("/Shuffleboard/Tab Title/List Layout/Data", entry.GetName()) + EXPECT_EQ("/Shuffleboard/Tab Title/List/Data", entry.GetName()) << "Entry path generated incorrectly"; } TEST_F(ShuffleboardInstanceTest, NestedLayoutsFluent) { auto entry = m_shuffleboardInstance->GetTab("Tab") - .GetLayout("List", "First") - .GetLayout("List", "Second") - .GetLayout("List", "Third") - .GetLayout("List", "Fourth") + .GetLayout("First", "List") + .GetLayout("Second", "List") + .GetLayout("Third", "List") + .GetLayout("Fourth", "List") .Add("Value", "string") .GetEntry(); @@ -60,10 +60,10 @@ TEST_F(ShuffleboardInstanceTest, NestedLayoutsFluent) { TEST_F(ShuffleboardInstanceTest, NestedLayoutsOop) { ShuffleboardTab& tab = m_shuffleboardInstance->GetTab("Tab"); - ShuffleboardLayout& first = tab.GetLayout("List", "First"); - ShuffleboardLayout& second = first.GetLayout("List", "Second"); - ShuffleboardLayout& third = second.GetLayout("List", "Third"); - ShuffleboardLayout& fourth = third.GetLayout("List", "Fourth"); + ShuffleboardLayout& first = tab.GetLayout("First", "List"); + ShuffleboardLayout& second = first.GetLayout("Second", "List"); + ShuffleboardLayout& third = second.GetLayout("Third", "List"); + ShuffleboardLayout& fourth = third.GetLayout("Fourth", "List"); SimpleWidget& widget = fourth.Add("Value", "string"); auto entry = widget.GetEntry(); @@ -75,17 +75,17 @@ TEST_F(ShuffleboardInstanceTest, NestedLayoutsOop) { TEST_F(ShuffleboardInstanceTest, LayoutTypeIsSet) { std::string layoutType = "Type"; - m_shuffleboardInstance->GetTab("Tab").GetLayout(layoutType, "Title"); + m_shuffleboardInstance->GetTab("Tab").GetLayout("Title", layoutType); m_shuffleboardInstance->Update(); nt::NetworkTableEntry entry = m_ntInstance.GetEntry( "/Shuffleboard/.metadata/Tab/Title/PreferredComponent"); EXPECT_EQ(layoutType, entry.GetString("Not Set")) << "Layout type not set"; } -TEST_F(ShuffleboardInstanceTest, NestedActuatoWidgetsAreDisabled) { +TEST_F(ShuffleboardInstanceTest, NestedActuatorWidgetsAreDisabled) { MockActuatorSendable sendable("Actuator"); m_shuffleboardInstance->GetTab("Tab") - .GetLayout("Layout", "Title") + .GetLayout("Title", "Layout") .Add(sendable); auto controllableEntry = m_ntInstance.GetEntry("/Shuffleboard/Tab/Title/Actuator/.controllable"); diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/BuiltInLayouts.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/BuiltInLayouts.java new file mode 100644 index 0000000000..27c3e8cd82 --- /dev/null +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/BuiltInLayouts.java @@ -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; + +/** + * The types of layouts bundled with Shuffleboard. + * + *
{@code
+ * ShuffleboardLayout myList = Shuffleboard.getTab("My Tab")
+ *   .getLayout(BuiltinLayouts.kList, "My List");
+ * }
+ */ +public enum BuiltInLayouts implements LayoutType { + /** + * Groups components in a vertical list. New widgets added to the layout will be placed at the + * bottom of the list. + *
Custom properties: + * + * + * + * + *
NameTypeDefault ValueNotes
Label positionString"BOTTOM"The position of component labels inside the grid. One of + * {@code ["TOP", "LEFT", "BOTTOM", "RIGHT", "HIDDEN"}
+ */ + kList("List Layout"), + + /** + * Groups components in an n x m grid. Grid layouts default to 3x3. + *
Custom properties: + * + * + * + * + * + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
Number of columnsNumber3Must be in the range [1,15]
Number of rowsNumber3Must be in the range [1,15]
Label positionString"BOTTOM"The position of component labels inside the grid. + * One of {@code ["TOP", "LEFT", "BOTTOM", "RIGHT", "HIDDEN"}
+ */ + kGrid("Grid Layout"), + ; + + private final String m_layoutName; + + BuiltInLayouts(String layoutName) { + m_layoutName = layoutName; + } + + @Override + public String getLayoutName() { + return m_layoutName; + } +} diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/BuiltInWidgets.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/BuiltInWidgets.java new file mode 100644 index 0000000000..14742e55db --- /dev/null +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/BuiltInWidgets.java @@ -0,0 +1,399 @@ +/*----------------------------------------------------------------------------*/ +/* 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.wpilibj.interfaces.Accelerometer.Range; + +/** + * The types of the widgets bundled with Shuffleboard. + * + *

For example, setting a number to be displayed with a slider: + *

{@code
+ * NetworkTableEntry example = Shuffleboard.getTab("My Tab")
+ *   .add("My Number", 0)
+ *   .withWidget(BuiltInWidgets.kNumberSlider)
+ *   .withProperties(Map.of("min", 0, "max", 1))
+ *   .getEntry();
+ * }
+ * + *

Each value in this enum goes into detail on what data types that widget can support, as well + * as the custom properties that widget uses. + */ +public enum BuiltInWidgets implements WidgetType { + /** + * Displays a value with a simple text field. + *
Supported types: + *

    + *
  • String
  • + *
  • Number
  • + *
  • Boolean
  • + *
+ *
This widget has no custom properties. + */ + kTextView("Text View"), + /** + * Displays a number with a controllable slider. + *
Supported types: + *
    + *
  • Number
  • + *
+ *
Custom properties: + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
MinNumber-1.0The minimum value of the slider
MaxNumber1.0The maximum value of the slider
Block incrementNumber0.0625How much to move the slider by with the arrow keys
+ */ + kNumberSlider("Number Slider"), + /** + * Displays a number with a view-only bar. + *
Supported types: + *
    + *
  • Number
  • + *
+ *
Custom properties: + * + * + * + * + * + *
NameTypeDefault ValueNotes
MinNumber-1.0The minimum value of the bar
MaxNumber1.0The maximum value of the bar
CenterNumber0The center ("zero") value of the bar
+ */ + kNumberBar("Number Bar"), + /** + * Displays a number with a view-only dial. Displayed values are rounded to the nearest integer. + *
Supported types: + *
    + *
  • Number
  • + *
+ *
Custom properties: + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
MinNumber0The minimum value of the dial
MaxNumber100The maximum value of the dial
Show valueBooleantrueWhether or not to show the value as text
+ */ + kDial("Simple Dial"), + /** + * Displays a number with a graph. NOTE: graphs can be taxing on the computer + * running the dashboard. Keep the number of visible data points to a minimum. Making the widget + * smaller also helps with performance, but may cause the graph to become difficult to read. + *
Supported types: + *
    + *
  • Number
  • + *
  • Number array
  • + *
+ *
Custom properties: + * + * + * + * + *
NameTypeDefault ValueNotes
Visible timeNumber30How long, in seconds, should past data be visible for
+ */ + kGraph("Graph"), + /** + * Displays a boolean value as a large colored box. + *
Supported types: + *
    + *
  • Boolean
  • + *
+ *
Custom properties: + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
Color when trueColor"green"Can be specified as a string ({@code "#00FF00"}) or a rgba integer ({@code 0x00FF0000}) + *
Color when falseColor"red"Can be specified as a string or a number
+ */ + kBooleanBox("Boolean Box"), + /** + * Displays a boolean with a large interactive toggle button. + *
Supported types: + *
    + *
  • Boolean
  • + *
+ *
This widget has no custom properties. + */ + kToggleButton("Toggle Button"), + /** + * Displays a boolean with a fixed-size toggle switch. + *
Supported types: + *
    + *
  • Boolean
  • + *
+ *
This widget has no custom properties. + */ + kToggleSwitch("Toggle Switch"), + /** + * Displays an analog input or a raw number with a number bar. + *
Supported types: + *
    + *
  • Number
  • + *
  • {@link edu.wpi.first.wpilibj.AnalogInput}
  • + *
+ *
Custom properties: + * + * + * + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
MinNumber0The minimum value of the bar
MaxNumber5The maximum value of the bar
CenterNumber0The center ("zero") value of the bar
OrientationString"HORIZONTAL"The orientation of the bar. One of {@code ["HORIZONTAL", "VERTICAL"]}
Number of tick marksNumber5The number of discrete ticks on the bar
+ */ + kVoltageView("Voltage View"), + /** + * Displays a {@link edu.wpi.first.wpilibj.PowerDistributionPanel PowerDistributionPanel}. + *
Supported types: + *
    + *
  • {@link edu.wpi.first.wpilibj.PowerDistributionPanel}
  • + *
+ *
Custom properties: + * + * + * + * + *
NameTypeDefault ValueNotes
Show voltage and current valuesBooleantrueWhether or not to display the voltage and current draw
+ */ + kPowerDistributionPanel("PDP"), + /** + * Displays a {@link edu.wpi.first.wpilibj.smartdashboard.SendableChooser SendableChooser} with + * a dropdown combo box with a list of options. + *
Supported types: + *
    + *
  • {@link edu.wpi.first.wpilibj.smartdashboard.SendableChooser}
  • + *
+ *
This widget has no custom properties. + */ + kComboBoxChooser("ComboBox Chooser"), + /** + * Displays a {@link edu.wpi.first.wpilibj.smartdashboard.SendableChooser SendableChooser} with + * a toggle button for each available option. + *
Supported types: + *
    + *
  • {@link edu.wpi.first.wpilibj.smartdashboard.SendableChooser}
  • + *
+ *
This widget has no custom properties. + */ + kSplitButtonChooser("Split Button Chooser"), + /** + * Displays an {@link edu.wpi.first.wpilibj.Encoder} displaying its speed, total travelled + * distance, and its distance per tick. + *
Supported types: + *
    + *
  • {@link edu.wpi.first.wpilibj.Encoder}
  • + *
+ *
This widget has no custom properties. + */ + kEncoder("Encoder"), + /** + * Displays a {@link edu.wpi.first.wpilibj.SpeedController SpeedController}. The speed controller + * will be controllable from the dashboard when test mode is enabled, but will otherwise be + * view-only. + *
Supported types: + *
    + *
  • {@link edu.wpi.first.wpilibj.PWMSpeedController}
  • + *
  • {@link edu.wpi.first.wpilibj.DMC60}
  • + *
  • {@link edu.wpi.first.wpilibj.Jaguar}
  • + *
  • {@link edu.wpi.first.wpilibj.PWMTalonSRX}
  • + *
  • {@link edu.wpi.first.wpilibj.PWMVictorSPX}
  • + *
  • {@link edu.wpi.first.wpilibj.SD540}
  • + *
  • {@link edu.wpi.first.wpilibj.Spark}
  • + *
  • {@link edu.wpi.first.wpilibj.Talon}
  • + *
  • {@link edu.wpi.first.wpilibj.Victor}
  • + *
  • {@link edu.wpi.first.wpilibj.VictorSP}
  • + *
  • {@link edu.wpi.first.wpilibj.SpeedControllerGroup}
  • + *
+ *
Custom properties: + * + * + * + * + *
NameTypeDefault ValueNotes
OrientationString"HORIZONTAL"One of {@code ["HORIZONTAL", "VERTICAL"]}
+ */ + kSpeedController("Speed Controller"), + /** + * Displays a command with a toggle button. Pressing the button will start the command, and the + * button will automatically release when the command completes. + *
Supported types: + *
    + *
  • {@link edu.wpi.first.wpilibj.command.Command}
  • + *
  • {@link edu.wpi.first.wpilibj.command.CommandGroup}
  • + *
  • Any custom subclass of {@code Command} or {@code CommandGroup}
  • + *
+ *
This widget has no custom properties. + */ + kCommand("Command"), + /** + * Displays a PID command with a checkbox and an editor for the PIDF constants. Selecting the + * checkbox will start the command, and the checkbox will automatically deselect when the command + * completes. + *
Supported types: + *
    + *
  • {@link edu.wpi.first.wpilibj.command.PIDCommand}
  • + *
  • Any custom subclass of {@code PIDCommand}
  • + *
+ *
This widget has no custom properties. + */ + kPIDCommand("PID Command"), + /** + * Displays a PID controller with an editor for the PIDF constants and a toggle switch for + * enabling and disabling the controller. + *
Supported types: + *
    + *
  • {@link edu.wpi.first.wpilibj.PIDController}
  • + *
+ *
This widget has no custom properties. + */ + kPIDController("PID Controller"), + /** + * Displays an accelerometer with a number bar displaying the magnitude of the acceleration and + * text displaying the exact value. + *
Supported types: + *
    + *
  • {@link edu.wpi.first.wpilibj.AnalogAccelerometer}
  • + *
+ *
Custom properties: + * + * + * + * + * + * + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
MinNumber-1The minimum acceleration value to display
MaxNumber1The maximum acceleration value to display
Show textBooleantrueShow or hide the acceleration values
PrecisionNumber2How many numbers to display after the decimal point
Show tick marksBooleanfalseShow or hide the tick marks on the number bars
+ */ + kAccelerometer("Accelerometer"), + /** + * Displays a 3-axis accelerometer with a number bar for each axis' accleration. + *
Supported types: + *
    + *
  • {@link edu.wpi.first.wpilibj.ADXL345_I2C}
  • + *
  • {@link edu.wpi.first.wpilibj.ADXL345_SPI}
  • + *
  • {@link edu.wpi.first.wpilibj.ADXL362}
  • + *
+ *
Custom properties: + * + * + * + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
Range{@link Range}k16GThe accelerometer range
Show valueBooleantrueShow or hide the acceleration values
PrecisionNumber2How many numbers to display after the decimal point
Show tick marksBooleanfalseShow or hide the tick marks on the number bars
+ */ + k3AxisAccelerometer("3-Axis Accelerometer"), + /** + * Displays a gyro with a dial from 0 to 360 degrees. + *
Supported types: + *
    + *
  • {@link edu.wpi.first.wpilibj.ADXRS450_Gyro}
  • + *
  • {@link edu.wpi.first.wpilibj.AnalogGyro}
  • + *
  • Any custom subclass of {@code GyroBase} (such as a MXP gyro)
  • + *
+ *
Custom properties: + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
Major tick spacingNumber45Degrees
Starting angleNumber180How far to rotate the entire dial, in degrees
Show tick mark ringBooleantrue
+ */ + kGyro("Gyro"), + /** + * Displays a relay with toggle buttons for each supported mode (off, on, forward, reverse). + *
Supported types: + *
    + *
  • {@link edu.wpi.first.wpilibj.Relay}
  • + *
+ *
This widget has no custom properties. + */ + kRelay("Relay"), + /** + * Displays a differential drive with a widget that displays the speed of each side of the + * drivebase and a vector for the direction and rotation of the drivebase. The widget will be + * controllable if the robot is in test mode. + *
Supported types: + *
    + *
  • {@link edu.wpi.first.wpilibj.drive.DifferentialDrive}
  • + *
+ *
Custom properties: + * + * + * + * + * + *
NameTypeDefault ValueNotes
Number of wheelsNumber4Must be a positive even integer + *
Wheel diameterNumber80Pixels
Show velocity vectorsBooleantrue
+ */ + kDifferentialDrive("Differential Drivebase"), + /** + * Displays a mecanum drive with a widget that displays the speed of each wheel, and vectors for + * the direction and rotation of the drivebase. The widget will be controllable if the robot is + * in test mode. + *
Supported types: + *
    + *
  • {@link edu.wpi.first.wpilibj.drive.MecanumDrive}
  • + *
+ *
Custom properties: + * + * + * + *
NameTypeDefault ValueNotes
Show velocity vectorsBooleantrue
+ */ + kMecanumDrive("Mecanum Drivebase"), + /** + * Displays a camera stream. + *
Supported types: + *
    + *
  • {@link edu.wpi.cscore.VideoSource} (as long as it is streaming on an MJPEG server)
  • + *
+ *
Custom properties: + * + * + * + * + * + * + * + * + * + *
NameTypeDefault ValueNotes
Show crosshairBooleantrueShow or hide a crosshair on the image
Crosshair colorColor"white"Can be a string or a rgba integer
Show controlsBooleantrueShow or hide the stream controls + *
RotationString"NONE"Rotates the displayed image. One of {@code ["NONE", "QUARTER_CW", "QUARTER_CCW", "HALF"]} + *
+ */ + kCameraStream("Camera Stream"), + ; + + private final String m_widgetName; + + BuiltInWidgets(String widgetName) { + this.m_widgetName = widgetName; + } + + @Override + public String getWidgetName() { + return m_widgetName; + } +} diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ContainerHelper.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ContainerHelper.java index d88c57b532..9cb63a09de 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ContainerHelper.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ContainerHelper.java @@ -12,6 +12,7 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; @@ -35,7 +36,7 @@ final class ContainerHelper { return m_components; } - ShuffleboardLayout getLayout(String type, String title) { + ShuffleboardLayout getLayout(String title, String type) { if (!m_layouts.containsKey(title)) { ShuffleboardLayout layout = new ShuffleboardLayout(m_container, type, title); m_components.add(layout); @@ -44,6 +45,14 @@ final class ContainerHelper { return m_layouts.get(title); } + ShuffleboardLayout getLayout(String title) { + ShuffleboardLayout layout = m_layouts.get(title); + if (layout == null) { + throw new NoSuchElementException("No layout has been defined with the title '" + title + "'"); + } + return layout; + } + ComplexWidget add(String title, Sendable sendable) { checkTitle(title); ComplexWidget widget = new ComplexWidget(m_container, title, sendable); diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/LayoutType.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/LayoutType.java new file mode 100644 index 0000000000..2d7b565347 --- /dev/null +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/LayoutType.java @@ -0,0 +1,21 @@ +/*----------------------------------------------------------------------------*/ +/* 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; + +/** + * Represents the type of a layout in Shuffleboard. Using this is preferred over specifying raw + * strings, to avoid typos and having to know or look up the exact string name for a desired layout. + * + * @see BuiltInWidgets the built-in widget types + */ +public interface LayoutType { + /** + * Gets the string type of the layout as defined by that layout in Shuffleboard. + */ + String getLayoutName(); +} diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardContainer.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardContainer.java index aa50f9f758..e3ac342998 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardContainer.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardContainer.java @@ -8,6 +8,7 @@ package edu.wpi.first.wpilibj.shuffleboard; import java.util.List; +import java.util.NoSuchElementException; import edu.wpi.first.wpilibj.Sendable; @@ -21,15 +22,48 @@ public interface ShuffleboardContainer extends ShuffleboardValue { */ List> 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. Note: this method should only be used to use a layout type that + * is not already built into Shuffleboard. To use a layout built into Shuffleboard, use + * {@link #getLayout(String, LayoutType)} and the layouts in {@link BuiltInLayouts}. + * + * @param title the title of the layout + * @param type the type of the layout, eg "List Layout" or "Grid Layout" + * @return the layout + * @see #getLayout(String, LayoutType) + */ + ShuffleboardLayout getLayout(String title, String type); + /** * 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 + * @param title the title of the layout + * @param layoutType the type of the layout, eg "List" or "Grid" * @return the layout */ - ShuffleboardLayout getLayout(String type, String title); + default ShuffleboardLayout getLayout(String title, LayoutType layoutType) { + return getLayout(title, layoutType.getLayoutName()); + } + + /** + * Gets the already-defined layout in this container with the given title. + * + *
{@code
+   * Shuffleboard.getTab("Example Tab")
+   *   .getLayout("My Layout", BuiltInLayouts.kList);
+   *
+   * // Later...
+   * Shuffleboard.getTab("Example Tab")
+   *   .getLayout("My Layout");
+   * }
+ * + * @param title the title of the layout to get + * @return the layout with the given title + * @throws NoSuchElementException if no layout has yet been defined with the given title + */ + ShuffleboardLayout getLayout(String title) throws NoSuchElementException; /** * Adds a widget to this container to display the given sendable. @@ -69,7 +103,7 @@ public interface ShuffleboardContainer extends ShuffleboardValue { * {@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 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 diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardLayout.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardLayout.java index 7c41035ed6..2a9d4253cd 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardLayout.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardLayout.java @@ -8,6 +8,7 @@ package edu.wpi.first.wpilibj.shuffleboard; import java.util.List; +import java.util.NoSuchElementException; import java.util.Objects; import edu.wpi.first.networktables.NetworkTable; @@ -30,8 +31,13 @@ public class ShuffleboardLayout extends ShuffleboardComponent> * * @param widgetType the type of the widget used to display the data * @return this widget object + * @see BuiltInWidgets + */ + public final W withWidget(WidgetType widgetType) { + return withWidget(widgetType.getWidgetName()); + } + + /** + * Sets the type of widget used to display the data. If not set, the default widget type will be + * used. This method should only be used to use a widget that does not come built into + * Shuffleboard (i.e. one that comes with a custom or thrid-party plugin). To use a widget that + * is built into Shuffleboard, use {@link #withWidget(WidgetType)} and {@link BuiltInWidgets}. + * + * @param widgetType the type of the widget used to display the data + * @return this widget object */ public final W withWidget(String widgetType) { setType(widgetType); diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/WidgetType.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/WidgetType.java new file mode 100644 index 0000000000..1e8242fa86 --- /dev/null +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/WidgetType.java @@ -0,0 +1,21 @@ +/*----------------------------------------------------------------------------*/ +/* 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; + +/** + * Represents the type of a widget in Shuffleboard. Using this is preferred over specifying raw + * strings, to avoid typos and having to know or look up the exact string name for a desired widget. + * + * @see BuiltInWidgets the built-in widget types + */ +public interface WidgetType { + /** + * Gets the string type of the widget as defined by that widget in Shuffleboard. + */ + String getWidgetName(); +} diff --git a/wpilibj/src/test/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardInstanceTest.java b/wpilibj/src/test/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardInstanceTest.java index dea8ed772a..9824f11261 100644 --- a/wpilibj/src/test/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardInstanceTest.java +++ b/wpilibj/src/test/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardInstanceTest.java @@ -37,24 +37,24 @@ public class ShuffleboardInstanceTest { @Test void testPathFluent() { NetworkTableEntry entry = m_shuffleboardInstance.getTab("Tab Title") - .getLayout("List", "List Layout") + .getLayout("Layout Title", "List Layout") .add("Data", "string") .withWidget("Text View") .getEntry(); assertAll( () -> assertEquals("string", entry.getString(null), "Wrong entry value"), - () -> assertEquals("/Shuffleboard/Tab Title/List Layout/Data", entry.getName(), + () -> assertEquals("/Shuffleboard/Tab Title/Layout Title/Data", entry.getName(), "Entry path generated incorrectly")); } @Test void testNestedLayoutsFluent() { NetworkTableEntry entry = m_shuffleboardInstance.getTab("Tab") - .getLayout("List", "First") - .getLayout("List", "Second") - .getLayout("List", "Third") - .getLayout("List", "Fourth") + .getLayout("First", "List") + .getLayout("Second", "List") + .getLayout("Third", "List") + .getLayout("Fourth", "List") .add("Value", "string") .getEntry(); @@ -67,10 +67,10 @@ public class ShuffleboardInstanceTest { @Test void testNestedLayoutsOop() { ShuffleboardTab tab = m_shuffleboardInstance.getTab("Tab"); - ShuffleboardLayout first = tab.getLayout("List", "First"); - ShuffleboardLayout second = first.getLayout("List", "Second"); - ShuffleboardLayout third = second.getLayout("List", "Third"); - ShuffleboardLayout fourth = third.getLayout("List", "Fourth"); + ShuffleboardLayout first = tab.getLayout("First", "List"); + ShuffleboardLayout second = first.getLayout("Second", "List"); + ShuffleboardLayout third = second.getLayout("Third", "List"); + ShuffleboardLayout fourth = third.getLayout("Fourth", "List"); SimpleWidget widget = fourth.add("Value", "string"); NetworkTableEntry entry = widget.getEntry(); @@ -84,7 +84,7 @@ public class ShuffleboardInstanceTest { void testLayoutTypeIsSet() { String layoutType = "Type"; m_shuffleboardInstance.getTab("Tab") - .getLayout(layoutType, "Title"); + .getLayout("Title", layoutType); m_shuffleboardInstance.update(); NetworkTableEntry entry = m_ntInstance.getEntry( "/Shuffleboard/.metadata/Tab/Title/PreferredComponent"); @@ -94,7 +94,7 @@ public class ShuffleboardInstanceTest { @Test void testNestedActuatorWidgetsAreDisabled() { m_shuffleboardInstance.getTab("Tab") - .getLayout("Layout", "Title") + .getLayout("Title", "Layout") .add(new MockActuatorSendable("Actuator")); NetworkTableEntry controllableEntry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title/Actuator/.controllable");