mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-04 03:11:43 +00:00
[wpilib] SendableChooser: Add onChange listener (#5458)
This commit is contained in:
@@ -31,7 +31,7 @@ template <class T>
|
||||
requires std::copy_constructible<T> && std::default_initializable<T>
|
||||
class SendableChooser : public SendableChooserBase {
|
||||
wpi::StringMap<T> m_choices;
|
||||
|
||||
std::function<void(T)> m_listener;
|
||||
template <class U>
|
||||
static U _unwrap_smart_ptr(const U& value);
|
||||
|
||||
@@ -82,6 +82,14 @@ class SendableChooser : public SendableChooserBase {
|
||||
*/
|
||||
auto GetSelected() -> decltype(_unwrap_smart_ptr(m_choices[""]));
|
||||
|
||||
/**
|
||||
* Bind a listener that's called when the selected value changes.
|
||||
* Only one listener can be bound. Calling this function will replace the
|
||||
* previous listener.
|
||||
* @param listener The function to call that accepts the new value
|
||||
*/
|
||||
void OnChange(std::function<void(T)>);
|
||||
|
||||
void InitSendable(nt::NTSendableBuilder& builder) override;
|
||||
};
|
||||
|
||||
|
||||
@@ -48,6 +48,13 @@ auto SendableChooser<T>::GetSelected()
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
requires std::copy_constructible<T> && std::default_initializable<T>
|
||||
void SendableChooser<T>::OnChange(std::function<void(T)> listener) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_listener = listener;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
requires std::copy_constructible<T> && std::default_initializable<T>
|
||||
void SendableChooser<T>::InitSendable(nt::NTSendableBuilder& builder) {
|
||||
@@ -95,11 +102,23 @@ void SendableChooser<T>::InitSendable(nt::NTSendableBuilder& builder) {
|
||||
nullptr);
|
||||
builder.AddStringProperty(kSelected, nullptr,
|
||||
[=, this](std::string_view val) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_haveSelected = true;
|
||||
m_selected = val;
|
||||
for (auto& pub : m_activePubs) {
|
||||
pub.Set(val);
|
||||
T choice{};
|
||||
std::function<void(T)> listener;
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_haveSelected = true;
|
||||
m_selected = val;
|
||||
for (auto& pub : m_activePubs) {
|
||||
pub.Set(val);
|
||||
}
|
||||
if (m_previousVal != val && m_listener) {
|
||||
choice = m_choices[val];
|
||||
listener = m_listener;
|
||||
}
|
||||
m_previousVal = val;
|
||||
}
|
||||
if (listener) {
|
||||
listener(choice);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ class SendableChooserBase : public nt::NTSendable,
|
||||
wpi::SmallVector<nt::StringPublisher, 2> m_activePubs;
|
||||
wpi::mutex m_mutex;
|
||||
int m_instance;
|
||||
std::string m_previousVal;
|
||||
static std::atomic_int s_instances;
|
||||
};
|
||||
|
||||
|
||||
@@ -56,5 +56,22 @@ TEST(SendableChooserTest,
|
||||
EXPECT_EQ(0, chooser.GetSelected());
|
||||
}
|
||||
|
||||
TEST(SendableChooserTest, ChangeListener) {
|
||||
frc::SendableChooser<int> chooser;
|
||||
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
chooser.AddOption(std::to_string(i), i);
|
||||
}
|
||||
int currentVal = 0;
|
||||
chooser.OnChange([&](int val) { currentVal = val; });
|
||||
|
||||
frc::SmartDashboard::PutData("chooser", &chooser);
|
||||
frc::SmartDashboard::UpdateValues();
|
||||
frc::SmartDashboard::PutString("chooser/selected", "3");
|
||||
frc::SmartDashboard::UpdateValues();
|
||||
|
||||
EXPECT_EQ(3, currentVal);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(SendableChooserTests, SendableChooserTest,
|
||||
::testing::Values(0, 1, 2, 3));
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* The {@link SendableChooser} class is a useful tool for presenting a selection of options to the
|
||||
@@ -48,6 +49,8 @@ public class SendableChooser<V> implements NTSendable, AutoCloseable {
|
||||
|
||||
private String m_defaultChoice = "";
|
||||
private final int m_instance;
|
||||
private String m_previousVal;
|
||||
private Consumer<V> m_listener;
|
||||
private static final AtomicInteger s_instances = new AtomicInteger();
|
||||
|
||||
/** Instantiates a {@link SendableChooser}. */
|
||||
@@ -114,6 +117,19 @@ public class SendableChooser<V> implements NTSendable, AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a listener that's called when the selected value changes. Only one listener can be bound.
|
||||
* Calling this function will replace the previous listener.
|
||||
*
|
||||
* @param listener The function to call that accepts the new value
|
||||
*/
|
||||
public void onChange(Consumer<V> listener) {
|
||||
requireNonNullParam(listener, "listener", "onChange");
|
||||
m_mutex.lock();
|
||||
m_listener = listener;
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
private String m_selected;
|
||||
private final List<StringPublisher> m_activePubs = new ArrayList<>();
|
||||
private final ReentrantLock m_mutex = new ReentrantLock();
|
||||
@@ -151,15 +167,28 @@ public class SendableChooser<V> implements NTSendable, AutoCloseable {
|
||||
SELECTED,
|
||||
null,
|
||||
val -> {
|
||||
V choice;
|
||||
Consumer<V> listener;
|
||||
m_mutex.lock();
|
||||
try {
|
||||
m_selected = val;
|
||||
if (!m_selected.equals(m_previousVal) && m_listener != null) {
|
||||
choice = m_map.get(val);
|
||||
listener = m_listener;
|
||||
} else {
|
||||
choice = null;
|
||||
listener = null;
|
||||
}
|
||||
m_previousVal = val;
|
||||
for (StringPublisher pub : m_activePubs) {
|
||||
pub.set(val);
|
||||
}
|
||||
} finally {
|
||||
m_mutex.unlock();
|
||||
}
|
||||
if (listener != null) {
|
||||
listener.accept(choice);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -64,6 +65,23 @@ class SendableChooserTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChangeListener() {
|
||||
try (var chooser = new SendableChooser<Integer>()) {
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
chooser.addOption(String.valueOf(i), i);
|
||||
}
|
||||
AtomicInteger currentVal = new AtomicInteger();
|
||||
chooser.onChange(val -> currentVal.set(val));
|
||||
|
||||
SmartDashboard.putData("chooser", chooser);
|
||||
SmartDashboard.updateValues();
|
||||
SmartDashboard.putString("chooser/selected", "3");
|
||||
SmartDashboard.updateValues();
|
||||
assertEquals(3, currentVal.get());
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
m_inst.close();
|
||||
|
||||
Reference in New Issue
Block a user