diff --git a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp index 4daf3ce6e4..ffcc5217ce 100644 --- a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp +++ b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp @@ -141,6 +141,108 @@ SimpleWidget& ShuffleboardContainer::Add( return Add(title, nt::Value::MakeStringArray(defaultValue)); } +SuppliedValueWidget& ShuffleboardContainer::AddString( + const wpi::Twine& title, std::function supplier) { + static auto setter = [](nt::NetworkTableEntry entry, std::string value) { + entry.SetString(value); + }; + + CheckTitle(title); + auto widget = std::make_unique>( + *this, title, supplier, setter); + auto ptr = widget.get(); + m_components.emplace_back(std::move(widget)); + return *ptr; +} + +SuppliedValueWidget& ShuffleboardContainer::AddNumber( + const wpi::Twine& title, std::function supplier) { + static auto setter = [](nt::NetworkTableEntry entry, double value) { + entry.SetDouble(value); + }; + + CheckTitle(title); + auto widget = std::make_unique>(*this, title, + supplier, setter); + auto ptr = widget.get(); + m_components.emplace_back(std::move(widget)); + return *ptr; +} + +SuppliedValueWidget& ShuffleboardContainer::AddBoolean( + const wpi::Twine& title, std::function supplier) { + static auto setter = [](nt::NetworkTableEntry entry, bool value) { + entry.SetBoolean(value); + }; + + CheckTitle(title); + auto widget = std::make_unique>(*this, title, + supplier, setter); + auto ptr = widget.get(); + m_components.emplace_back(std::move(widget)); + return *ptr; +} + +SuppliedValueWidget>& +ShuffleboardContainer::AddStringArray( + const wpi::Twine& title, + std::function()> supplier) { + static auto setter = [](nt::NetworkTableEntry entry, + std::vector value) { + entry.SetStringArray(value); + }; + + CheckTitle(title); + auto widget = std::make_unique>>( + *this, title, supplier, setter); + auto ptr = widget.get(); + m_components.emplace_back(std::move(widget)); + return *ptr; +} + +SuppliedValueWidget>& ShuffleboardContainer::AddNumberArray( + const wpi::Twine& title, std::function()> supplier) { + static auto setter = [](nt::NetworkTableEntry entry, + std::vector value) { + entry.SetDoubleArray(value); + }; + + CheckTitle(title); + auto widget = std::make_unique>>( + *this, title, supplier, setter); + auto ptr = widget.get(); + m_components.emplace_back(std::move(widget)); + return *ptr; +} + +SuppliedValueWidget>& ShuffleboardContainer::AddBooleanArray( + const wpi::Twine& title, std::function()> supplier) { + static auto setter = [](nt::NetworkTableEntry entry, std::vector value) { + entry.SetBooleanArray(value); + }; + + CheckTitle(title); + auto widget = std::make_unique>>( + *this, title, supplier, setter); + auto ptr = widget.get(); + m_components.emplace_back(std::move(widget)); + return *ptr; +} + +SuppliedValueWidget& ShuffleboardContainer::AddRaw( + const wpi::Twine& title, std::function supplier) { + static auto setter = [](nt::NetworkTableEntry entry, wpi::StringRef value) { + entry.SetRaw(value); + }; + + CheckTitle(title); + auto widget = std::make_unique>( + *this, title, supplier, setter); + auto ptr = widget.get(); + m_components.emplace_back(std::move(widget)); + return *ptr; +} + SimpleWidget& ShuffleboardContainer::AddPersistent( const wpi::Twine& title, std::shared_ptr defaultValue) { auto& widget = Add(title, defaultValue); diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h index 8f94b20d46..d7a11c0da0 100644 --- a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h +++ b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -24,6 +25,7 @@ #include "frc/shuffleboard/LayoutType.h" #include "frc/shuffleboard/ShuffleboardComponentBase.h" #include "frc/shuffleboard/ShuffleboardValue.h" +#include "frc/shuffleboard/SuppliedValueWidget.h" namespace cs { class VideoSource; @@ -262,6 +264,98 @@ class ShuffleboardContainer : public virtual ShuffleboardValue, SimpleWidget& Add(const wpi::Twine& title, wpi::ArrayRef defaultValue); + /** + * Adds a widget to this container. The widget will display the data provided + * by the value supplier. Changes made on the dashboard will not propagate to + * the widget object, and will be overridden by values from the value + * supplier. + * + * @param title the title of the widget + * @param valueSupplier the supplier for values + * @return a widget to display data + */ + SuppliedValueWidget& AddString( + const wpi::Twine& title, std::function supplier); + + /** + * Adds a widget to this container. The widget will display the data provided + * by the value supplier. Changes made on the dashboard will not propagate to + * the widget object, and will be overridden by values from the value + * supplier. + * + * @param title the title of the widget + * @param valueSupplier the supplier for values + * @return a widget to display data + */ + SuppliedValueWidget& AddNumber(const wpi::Twine& title, + std::function supplier); + + /** + * Adds a widget to this container. The widget will display the data provided + * by the value supplier. Changes made on the dashboard will not propagate to + * the widget object, and will be overridden by values from the value + * supplier. + * + * @param title the title of the widget + * @param valueSupplier the supplier for values + * @return a widget to display data + */ + SuppliedValueWidget& AddBoolean(const wpi::Twine& title, + std::function supplier); + + /** + * Adds a widget to this container. The widget will display the data provided + * by the value supplier. Changes made on the dashboard will not propagate to + * the widget object, and will be overridden by values from the value + * supplier. + * + * @param title the title of the widget + * @param valueSupplier the supplier for values + * @return a widget to display data + */ + SuppliedValueWidget>& AddStringArray( + const wpi::Twine& title, + std::function()> supplier); + + /** + * Adds a widget to this container. The widget will display the data provided + * by the value supplier. Changes made on the dashboard will not propagate to + * the widget object, and will be overridden by values from the value + * supplier. + * + * @param title the title of the widget + * @param valueSupplier the supplier for values + * @return a widget to display data + */ + SuppliedValueWidget>& AddNumberArray( + const wpi::Twine& title, std::function()> supplier); + + /** + * Adds a widget to this container. The widget will display the data provided + * by the value supplier. Changes made on the dashboard will not propagate to + * the widget object, and will be overridden by values from the value + * supplier. + * + * @param title the title of the widget + * @param valueSupplier the supplier for values + * @return a widget to display data + */ + SuppliedValueWidget>& AddBooleanArray( + const wpi::Twine& title, std::function()> supplier); + + /** + * Adds a widget to this container. The widget will display the data provided + * by the value supplier. Changes made on the dashboard will not propagate to + * the widget object, and will be overridden by values from the value + * supplier. + * + * @param title the title of the widget + * @param valueSupplier the supplier for values + * @return a widget to display data + */ + SuppliedValueWidget& AddRaw( + const wpi::Twine& title, std::function supplier); + /** * Adds a widget to this container to display a simple piece of data. * diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/SuppliedValueWidget.h b/wpilibc/src/main/native/include/frc/shuffleboard/SuppliedValueWidget.h new file mode 100644 index 0000000000..c806db4cd4 --- /dev/null +++ b/wpilibc/src/main/native/include/frc/shuffleboard/SuppliedValueWidget.h @@ -0,0 +1,48 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 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 +#include + +#include +#include +#include + +#include "frc/shuffleboard/ShuffleboardComponent.h" +#include "frc/shuffleboard/ShuffleboardComponent.inc" +#include "frc/shuffleboard/ShuffleboardComponentBase.h" +#include "frc/shuffleboard/ShuffleboardContainer.h" +#include "frc/shuffleboard/ShuffleboardWidget.h" + +namespace frc { +template +class SuppliedValueWidget : public ShuffleboardWidget > { + public: + SuppliedValueWidget(ShuffleboardContainer& parent, const wpi::Twine& title, + std::function supplier, + std::function setter) + : ShuffleboardValue(title), + ShuffleboardWidget >(parent, title), + m_supplier(supplier), + m_setter(setter) {} + + void BuildInto(std::shared_ptr parentTable, + std::shared_ptr metaTable) override { + this->BuildMetadata(metaTable); + metaTable->GetEntry("Controllable").SetBoolean(false); + + auto entry = parentTable->GetEntry(this->GetTitle()); + m_setter(entry, m_supplier()); + } + + private: + std::function m_supplier; + std::function m_setter; +}; +} // namespace frc diff --git a/wpilibc/src/test/native/cpp/shuffleboard/SuppliedValueWidgetTest.cpp b/wpilibc/src/test/native/cpp/shuffleboard/SuppliedValueWidgetTest.cpp new file mode 100644 index 0000000000..8e39915b15 --- /dev/null +++ b/wpilibc/src/test/native/cpp/shuffleboard/SuppliedValueWidgetTest.cpp @@ -0,0 +1,107 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018-2019 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 +#include + +#include "frc/shuffleboard/ShuffleboardInstance.h" +#include "frc/shuffleboard/ShuffleboardTab.h" +#include "gtest/gtest.h" + +using namespace frc; + +class SuppliedValueWidgetTest : public testing::Test { + void SetUp() override { + m_ntInstance = nt::NetworkTableInstance::Create(); + m_instance = std::make_unique(m_ntInstance); + m_tab = &(m_instance->GetTab("Tab")); + } + + protected: + nt::NetworkTableInstance m_ntInstance; + ShuffleboardTab* m_tab; + std::unique_ptr m_instance; +}; + +TEST_F(SuppliedValueWidgetTest, AddString) { + std::string str = "foo"; + m_tab->AddString("String", [&str]() { return str; }); + auto entry = m_ntInstance.GetEntry("/Shuffleboard/Tab/String"); + + m_instance->Update(); + EXPECT_EQ("foo", entry.GetValue()->GetString()); +} + +TEST_F(SuppliedValueWidgetTest, AddNumber) { + int num = 0; + m_tab->AddNumber("Num", [&num]() { return ++num; }); + auto entry = m_ntInstance.GetEntry("/Shuffleboard/Tab/Num"); + + m_instance->Update(); + EXPECT_FLOAT_EQ(1.0, entry.GetValue()->GetDouble()); +} + +TEST_F(SuppliedValueWidgetTest, AddBoolean) { + bool value = true; + m_tab->AddBoolean("Bool", [&value]() { return value; }); + auto entry = m_ntInstance.GetEntry("/Shuffleboard/Tab/Bool"); + + m_instance->Update(); + EXPECT_EQ(true, entry.GetValue()->GetBoolean()); +} + +TEST_F(SuppliedValueWidgetTest, AddStringArray) { + std::vector strings = {"foo", "bar"}; + m_tab->AddStringArray("Strings", [&strings]() { return strings; }); + auto entry = m_ntInstance.GetEntry("/Shuffleboard/Tab/Strings"); + + m_instance->Update(); + auto actual = entry.GetValue()->GetStringArray(); + + EXPECT_EQ(strings.size(), actual.size()); + for (size_t i = 0; i < strings.size(); i++) { + EXPECT_EQ(strings[i], actual[i]); + } +} + +TEST_F(SuppliedValueWidgetTest, AddNumberArray) { + std::vector nums = {0, 1, 2, 3}; + m_tab->AddNumberArray("Numbers", [&nums]() { return nums; }); + auto entry = m_ntInstance.GetEntry("/Shuffleboard/Tab/Numbers"); + + m_instance->Update(); + auto actual = entry.GetValue()->GetDoubleArray(); + + EXPECT_EQ(nums.size(), actual.size()); + for (size_t i = 0; i < nums.size(); i++) { + EXPECT_FLOAT_EQ(nums[i], actual[i]); + } +} + +TEST_F(SuppliedValueWidgetTest, AddBooleanArray) { + std::vector bools = {true, false}; + m_tab->AddBooleanArray("Booleans", [&bools]() { return bools; }); + auto entry = m_ntInstance.GetEntry("/Shuffleboard/Tab/Booleans"); + + m_instance->Update(); + auto actual = entry.GetValue()->GetBooleanArray(); + + EXPECT_EQ(bools.size(), actual.size()); + for (size_t i = 0; i < bools.size(); i++) { + EXPECT_FLOAT_EQ(bools[i], actual[i]); + } +} + +TEST_F(SuppliedValueWidgetTest, AddRaw) { + wpi::StringRef bytes = "\1\2\3"; + m_tab->AddRaw("Raw", [&bytes]() { return bytes; }); + auto entry = m_ntInstance.GetEntry("/Shuffleboard/Tab/Raw"); + + m_instance->Update(); + auto actual = entry.GetValue()->GetRaw(); + EXPECT_EQ(bytes, actual); +} 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 9cb63a09de..0fb1e57c86 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 @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2018-2019 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. */ @@ -15,6 +15,10 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BooleanSupplier; +import java.util.function.DoubleSupplier; +import java.util.function.Supplier; import edu.wpi.first.networktables.NetworkTableEntry; import edu.wpi.first.wpilibj.Sendable; @@ -22,6 +26,7 @@ import edu.wpi.first.wpilibj.Sendable; /** * A helper class for Shuffleboard containers to handle common child operations. */ +@SuppressWarnings("PMD.TooManyMethods") final class ContainerHelper { private final ShuffleboardContainer m_container; private final Set m_usedTitles = new HashSet<>(); @@ -79,6 +84,55 @@ final class ContainerHelper { return widget; } + SuppliedValueWidget addString(String title, Supplier valueSupplier) { + precheck(title, valueSupplier); + return addSupplied(title, valueSupplier, NetworkTableEntry::setString); + } + + SuppliedValueWidget addNumber(String title, DoubleSupplier valueSupplier) { + precheck(title, valueSupplier); + return addSupplied(title, valueSupplier::getAsDouble, NetworkTableEntry::setDouble); + } + + SuppliedValueWidget addBoolean(String title, BooleanSupplier valueSupplier) { + precheck(title, valueSupplier); + return addSupplied(title, valueSupplier::getAsBoolean, NetworkTableEntry::setBoolean); + } + + SuppliedValueWidget addStringArray(String title, Supplier valueSupplier) { + precheck(title, valueSupplier); + return addSupplied(title, valueSupplier, NetworkTableEntry::setStringArray); + } + + SuppliedValueWidget addDoubleArray(String title, Supplier valueSupplier) { + precheck(title, valueSupplier); + return addSupplied(title, valueSupplier, NetworkTableEntry::setDoubleArray); + } + + SuppliedValueWidget addBooleanArray(String title, Supplier valueSupplier) { + precheck(title, valueSupplier); + return addSupplied(title, valueSupplier, NetworkTableEntry::setBooleanArray); + } + + SuppliedValueWidget addRaw(String title, Supplier valueSupplier) { + precheck(title, valueSupplier); + return addSupplied(title, valueSupplier, NetworkTableEntry::setRaw); + } + + private void precheck(String title, Object valueSupplier) { + Objects.requireNonNull(title, "Title cannot be null"); + Objects.requireNonNull(valueSupplier, "Value supplier cannot be null"); + checkTitle(title); + } + + private SuppliedValueWidget addSupplied(String title, + Supplier supplier, + BiConsumer setter) { + SuppliedValueWidget widget = new SuppliedValueWidget<>(m_container, title, supplier, setter); + m_components.add(widget); + return widget; + } + private static void checkNtType(Object data) { if (!NetworkTableEntry.isValidDataType(data)) { throw new IllegalArgumentException( 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 a322af18df..331b7f198b 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 @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2018-2019 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. */ @@ -9,6 +9,9 @@ package edu.wpi.first.wpilibj.shuffleboard; import java.util.List; import java.util.NoSuchElementException; +import java.util.function.BooleanSupplier; +import java.util.function.DoubleSupplier; +import java.util.function.Supplier; import edu.wpi.cscore.VideoSource; import edu.wpi.first.wpilibj.Sendable; @@ -16,6 +19,7 @@ import edu.wpi.first.wpilibj.Sendable; /** * Common interface for objects that can contain shuffleboard components. */ +@SuppressWarnings("PMD.TooManyMethods") public interface ShuffleboardContainer extends ShuffleboardValue { /** @@ -80,8 +84,8 @@ public interface ShuffleboardContainer extends ShuffleboardValue { /** * Adds a widget to this container to display the given video stream. * - * @param title the title of the widget - * @param video the video stream to display + * @param title the title of the widget + * @param video the video stream to display * @return a widget to display the sendable data * @throws IllegalArgumentException if a widget already exists in this container with the given * title @@ -124,6 +128,104 @@ public interface ShuffleboardContainer extends ShuffleboardValue { */ SimpleWidget add(String title, Object defaultValue) throws IllegalArgumentException; + /** + * Adds a widget to this container. The widget will display the data provided by the value + * supplier. Changes made on the dashboard will not propagate to the widget object, and will be + * overridden by values from the value supplier. + * + * @param title the title of the widget + * @param valueSupplier the supplier for values + * @return a widget to display data + * @throws IllegalArgumentException if a widget already exists in this container with the given + * title + */ + SuppliedValueWidget addString(String title, Supplier valueSupplier) + throws IllegalArgumentException; + + /** + * Adds a widget to this container. The widget will display the data provided by the value + * supplier. Changes made on the dashboard will not propagate to the widget object, and will be + * overridden by values from the value supplier. + * + * @param title the title of the widget + * @param valueSupplier the supplier for values + * @return a widget to display data + * @throws IllegalArgumentException if a widget already exists in this container with the given + * title + */ + SuppliedValueWidget addNumber(String title, DoubleSupplier valueSupplier) + throws IllegalArgumentException; + + /** + * Adds a widget to this container. The widget will display the data provided by the value + * supplier. Changes made on the dashboard will not propagate to the widget object, and will be + * overridden by values from the value supplier. + * + * @param title the title of the widget + * @param valueSupplier the supplier for values + * @return a widget to display data + * @throws IllegalArgumentException if a widget already exists in this container with the given + * title + */ + SuppliedValueWidget addBoolean(String title, BooleanSupplier valueSupplier) + throws IllegalArgumentException; + + /** + * Adds a widget to this container. The widget will display the data provided by the value + * supplier. Changes made on the dashboard will not propagate to the widget object, and will be + * overridden by values from the value supplier. + * + * @param title the title of the widget + * @param valueSupplier the supplier for values + * @return a widget to display data + * @throws IllegalArgumentException if a widget already exists in this container with the given + * title + */ + SuppliedValueWidget addStringArray(String title, Supplier valueSupplier) + throws IllegalArgumentException; + + /** + * Adds a widget to this container. The widget will display the data provided by the value + * supplier. Changes made on the dashboard will not propagate to the widget object, and will be + * overridden by values from the value supplier. + * + * @param title the title of the widget + * @param valueSupplier the supplier for values + * @return a widget to display data + * @throws IllegalArgumentException if a widget already exists in this container with the given + * title + */ + SuppliedValueWidget addDoubleArray(String title, Supplier valueSupplier) + throws IllegalArgumentException; + + /** + * Adds a widget to this container. The widget will display the data provided by the value + * supplier. Changes made on the dashboard will not propagate to the widget object, and will be + * overridden by values from the value supplier. + * + * @param title the title of the widget + * @param valueSupplier the supplier for values + * @return a widget to display data + * @throws IllegalArgumentException if a widget already exists in this container with the given + * title + */ + SuppliedValueWidget addBooleanArray(String title, Supplier valueSupplier) + throws IllegalArgumentException; + + /** + * Adds a widget to this container. The widget will display the data provided by the value + * supplier. Changes made on the dashboard will not propagate to the widget object, and will be + * overridden by values from the value supplier. + * + * @param title the title of the widget + * @param valueSupplier the supplier for values + * @return a widget to display data + * @throws IllegalArgumentException if a widget already exists in this container with the given + * title + */ + SuppliedValueWidget addRaw(String title, Supplier valueSupplier) + 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 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 2a9d4253cd..dbb621636a 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 @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2018-2019 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. */ @@ -10,6 +10,9 @@ package edu.wpi.first.wpilibj.shuffleboard; import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.function.BooleanSupplier; +import java.util.function.DoubleSupplier; +import java.util.function.Supplier; import edu.wpi.first.networktables.NetworkTable; import edu.wpi.first.wpilibj.Sendable; @@ -17,6 +20,7 @@ import edu.wpi.first.wpilibj.Sendable; /** * A layout in a Shuffleboard tab. Layouts can contain widgets and other layouts. */ +@SuppressWarnings("PMD.TooManyMethods") public class ShuffleboardLayout extends ShuffleboardComponent implements ShuffleboardContainer { private final ContainerHelper m_helper = new ContainerHelper(this); @@ -55,6 +59,55 @@ public class ShuffleboardLayout extends ShuffleboardComponent addString(String title, + Supplier valueSupplier) + throws IllegalArgumentException { + return m_helper.addString(title, valueSupplier); + } + + @Override + public SuppliedValueWidget addNumber(String title, + DoubleSupplier valueSupplier) + throws IllegalArgumentException { + return m_helper.addNumber(title, valueSupplier); + } + + @Override + public SuppliedValueWidget addBoolean(String title, + BooleanSupplier valueSupplier) + throws IllegalArgumentException { + return m_helper.addBoolean(title, valueSupplier); + } + + @Override + public SuppliedValueWidget addStringArray(String title, + Supplier valueSupplier) + throws IllegalArgumentException { + return m_helper.addStringArray(title, valueSupplier); + } + + @Override + public SuppliedValueWidget addDoubleArray(String title, + Supplier valueSupplier) + throws IllegalArgumentException { + return m_helper.addDoubleArray(title, valueSupplier); + } + + @Override + public SuppliedValueWidget addBooleanArray(String title, + Supplier valueSupplier) + throws IllegalArgumentException { + return m_helper.addBooleanArray(title, valueSupplier); + } + + @Override + public SuppliedValueWidget addRaw(String title, + Supplier valueSupplier) + throws IllegalArgumentException { + return m_helper.addRaw(title, valueSupplier); + } + @Override public void buildInto(NetworkTable parentTable, NetworkTable metaTable) { buildMetadata(metaTable); diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardTab.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardTab.java index aec70e2631..b46c6d2c76 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardTab.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardTab.java @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2018-2019 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. */ @@ -9,6 +9,9 @@ package edu.wpi.first.wpilibj.shuffleboard; import java.util.List; import java.util.NoSuchElementException; +import java.util.function.BooleanSupplier; +import java.util.function.DoubleSupplier; +import java.util.function.Supplier; import edu.wpi.first.networktables.NetworkTable; import edu.wpi.first.wpilibj.Sendable; @@ -19,6 +22,7 @@ import edu.wpi.first.wpilibj.Sendable; * 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). */ +@SuppressWarnings("PMD.TooManyMethods") public final class ShuffleboardTab implements ShuffleboardContainer { private final ContainerHelper m_helper = new ContainerHelper(this); private final ShuffleboardRoot m_root; @@ -68,6 +72,55 @@ public final class ShuffleboardTab implements ShuffleboardContainer { return m_helper.add(title, defaultValue); } + @Override + public SuppliedValueWidget addString(String title, + Supplier valueSupplier) + throws IllegalArgumentException { + return m_helper.addString(title, valueSupplier); + } + + @Override + public SuppliedValueWidget addNumber(String title, + DoubleSupplier valueSupplier) + throws IllegalArgumentException { + return m_helper.addNumber(title, valueSupplier); + } + + @Override + public SuppliedValueWidget addBoolean(String title, + BooleanSupplier valueSupplier) + throws IllegalArgumentException { + return m_helper.addBoolean(title, valueSupplier); + } + + @Override + public SuppliedValueWidget addStringArray(String title, + Supplier valueSupplier) + throws IllegalArgumentException { + return m_helper.addStringArray(title, valueSupplier); + } + + @Override + public SuppliedValueWidget addDoubleArray(String title, + Supplier valueSupplier) + throws IllegalArgumentException { + return m_helper.addDoubleArray(title, valueSupplier); + } + + @Override + public SuppliedValueWidget addBooleanArray(String title, + Supplier valueSupplier) + throws IllegalArgumentException { + return m_helper.addBooleanArray(title, valueSupplier); + } + + @Override + public SuppliedValueWidget addRaw(String title, + Supplier valueSupplier) + throws IllegalArgumentException { + return m_helper.addRaw(title, valueSupplier); + } + @Override public void buildInto(NetworkTable parentTable, NetworkTable metaTable) { NetworkTable tabTable = parentTable.getSubTable(m_title); diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/SuppliedValueWidget.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/SuppliedValueWidget.java new file mode 100644 index 0000000000..c7c9e8b851 --- /dev/null +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/SuppliedValueWidget.java @@ -0,0 +1,50 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 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.function.BiConsumer; +import java.util.function.Supplier; + +import edu.wpi.first.networktables.NetworkTable; +import edu.wpi.first.networktables.NetworkTableEntry; + +/** + * A Shuffleboard widget whose value is provided by user code. + * + * @param the type of values in the widget + */ +public final class SuppliedValueWidget extends ShuffleboardWidget> { + private final Supplier m_supplier; + private final BiConsumer m_setter; + + /** + * Package-private constructor for use by the Shuffleboard API. + * + * @param parent the parent container for the widget + * @param title the title of the widget + * @param supplier the supplier for values to place in the NetworkTable entry + * @param setter the function for placing values in the NetworkTable entry + */ + SuppliedValueWidget(ShuffleboardContainer parent, + String title, + Supplier supplier, + BiConsumer setter) { + super(parent, title); + this.m_supplier = supplier; + this.m_setter = setter; + } + + @Override + public void buildInto(NetworkTable parentTable, NetworkTable metaTable) { + buildMetadata(metaTable); + metaTable.getEntry("Controllable").setBoolean(false); + + NetworkTableEntry entry = parentTable.getEntry(getTitle()); + m_setter.accept(entry, m_supplier.get()); + } +} diff --git a/wpilibj/src/test/java/edu/wpi/first/wpilibj/shuffleboard/SuppliedValueWidgetTest.java b/wpilibj/src/test/java/edu/wpi/first/wpilibj/shuffleboard/SuppliedValueWidgetTest.java new file mode 100644 index 0000000000..6e0da8e280 --- /dev/null +++ b/wpilibj/src/test/java/edu/wpi/first/wpilibj/shuffleboard/SuppliedValueWidgetTest.java @@ -0,0 +1,119 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 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.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import edu.wpi.first.networktables.NetworkTableEntry; +import edu.wpi.first.networktables.NetworkTableInstance; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SuppliedValueWidgetTest { + private NetworkTableInstance m_ntInstance; + private ShuffleboardInstance m_instance; + + @BeforeEach + void setup() { + m_ntInstance = NetworkTableInstance.create(); + m_instance = new ShuffleboardInstance(m_ntInstance); + } + + @Test + void testAddString() { + AtomicInteger count = new AtomicInteger(0); + m_instance.getTab("Tab") + .addString("Title", () -> Integer.toString(count.incrementAndGet())); + NetworkTableEntry entry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title"); + + m_instance.update(); + assertEquals("1", entry.getString(null)); + + m_instance.update(); + assertEquals("2", entry.getString(null)); + } + + @Test + void testAddDouble() { + AtomicInteger num = new AtomicInteger(0); + m_instance.getTab("Tab") + .addNumber("Title", num::incrementAndGet); + NetworkTableEntry entry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title"); + + m_instance.update(); + assertEquals(1, entry.getDouble(0)); + + m_instance.update(); + assertEquals(2, entry.getDouble(0)); + } + + @Test + void testAddBoolean() { + boolean[] bool = {false}; + m_instance.getTab("Tab") + .addBoolean("Title", () -> bool[0] = !bool[0]); + NetworkTableEntry entry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title"); + + m_instance.update(); + assertTrue(entry.getBoolean(false)); + + m_instance.update(); + assertFalse(entry.getBoolean(true)); + } + + @Test + void testAddStringArray() { + String[] arr = {"foo", "bar"}; + m_instance.getTab("Tab") + .addStringArray("Title", () -> arr); + NetworkTableEntry entry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title"); + + m_instance.update(); + assertArrayEquals(arr, entry.getStringArray(new String[0])); + } + + @Test + void testAddDoubleArray() { + double[] arr = {0, 1}; + m_instance.getTab("Tab") + .addDoubleArray("Title", () -> arr); + NetworkTableEntry entry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title"); + + m_instance.update(); + assertArrayEquals(arr, entry.getDoubleArray(new double[0])); + } + + @Test + void testAddBooleanArray() { + boolean[] arr = {true, false}; + m_instance.getTab("Tab") + .addBooleanArray("Title", () -> arr); + NetworkTableEntry entry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title"); + + m_instance.update(); + assertArrayEquals(arr, entry.getBooleanArray(new boolean[0])); + } + + @Test + void testAddRawBytes() { + byte[] arr = {0, 1, 2, 3}; + m_instance.getTab("Tab") + .addRaw("Title", () -> arr); + NetworkTableEntry entry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title"); + + m_instance.update(); + assertArrayEquals(arr, entry.getRaw(new byte[0])); + } + +}