diff --git a/Makefile b/Makefile index 10d24a2..f697585 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,10 @@ BINDIR=$(ROOT)/bin SRCDIR=$(ROOT)/src INCDIR=$(ROOT)/include -WARNFLAGS+= +WARNFLAGS+= -Wall -Wextra -Wpedantic +ifndef ALLOW_WARN +WARNFLAGS+= -Werror +endif EXTRA_CFLAGS= EXTRA_CXXFLAGS= @@ -25,7 +28,6 @@ EXCLUDE_COLD_LIBRARIES:= # Set this to 1 to add additional rules to compile your project as a PROS library template IS_LIBRARY:=1 -# TODO: CHANGE THIS! LIBNAME:=gamepad VERSION:=0.0.1 # EXCLUDE_SRC_FROM_LIB= $(SRCDIR)/unpublishedfile.c diff --git a/include/gamepad/api.hpp b/include/gamepad/api.hpp index 835385a..1716600 100644 --- a/include/gamepad/api.hpp +++ b/include/gamepad/api.hpp @@ -1,4 +1,5 @@ #pragma once #include "gamepad/event_handler.hpp" // IWYU pragma: export +#include "gamepad/bindings.hpp" // IWYU pragma: export #include "gamepad/controller.hpp" // IWYU pragma: export \ No newline at end of file diff --git a/include/gamepad/bindings.hpp b/include/gamepad/bindings.hpp new file mode 100644 index 0000000..8bffd94 --- /dev/null +++ b/include/gamepad/bindings.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "controller.hpp" + +namespace Gamepad::Bindings { + +bool motorTwoButton(std::string name, pros::Motor motor, Button& fwd_button, Button& rev_button); +bool motorOneButton(std::string name, pros::Motor motor, Button& toggle_button, uint32_t speed = 127); +} // namespace Gamepad::Bindings \ No newline at end of file diff --git a/include/gamepad/button.hpp b/include/gamepad/button.hpp new file mode 100644 index 0000000..1f1fb15 --- /dev/null +++ b/include/gamepad/button.hpp @@ -0,0 +1,178 @@ +#include "event_handler.hpp" +#include "pros/rtos.hpp" +#include +#include + +namespace Gamepad { + +enum EventType { + ON_PRESS, + ON_LONG_PRESS, + ON_RELEASE, + ON_SHORT_RELEASE, +}; + +class Button { + friend class Controller; + public: + /// Whether the button has just been pressed + bool rising_edge = false; + /// Whether the button has just been released + bool falling_edge = false; + /// Whether the button is currently held down + bool is_pressed = false; + /// How long the button has been held down + uint32_t time_held = 0; + /// How long the button has been released + uint32_t time_released = 0; + /// How long the threshold should be for the longPress and shortRelease events + uint32_t long_press_threshold = 500; + /** + * @brief Register a function to run when the button is pressed. + * + * @param listenerName The name of the listener, this must be a unique name + * @param func The function to run when the button is pressed, the function MUST NOT block + * @return true The listener was successfully registered + * @return false The listener was not successfully registered (there is already a listener with this name) + * + * @b Example: + * @code {.cpp} + * // Use a function... + * Gamepad::master.Down.onPress("downPress1", downPress1); + * // ...or a lambda + * Gamepad::master.Up.onPress("upPress1", []() { std::cout << "I was pressed!" << std::endl; }); + * @endcode + */ + bool onPress(std::string listenerName, std::function func) const; + /** + * @brief Register a function to run when the button is long pressed. + * + * By default, onLongPress will fire when the button has been held down for + * 500ms or more, this threshold can be adjusted by changing long_press_threshold. + * + * @warning When using this event along with onPress, both the onPress + * and onlongPress listeners may fire together. + * + * @param listenerName The name of the listener, this must be a unique name + * @param func The function to run when the button is long pressed, the function MUST NOT block + * @return true The listener was successfully registered + * @return false The listener was not successfully registered (there is already a listener with this name) + * + * @b Example: + * @code {.cpp} + * // Use a function... + * Gamepad::master.Left.onLongPress("fireCatapult", fireCatapult); + * // ...or a lambda + * Gamepad::master.Right.onLongPress("print_right", []() { std::cout << "Right button was long pressed!" << + * std::endl; }); + * @endcode + */ + bool onLongPress(std::string listenerName, std::function func) const; + /** + * @brief Register a function to run when the button is released. + * + * @param listenerName The name of the listener, this must be a unique name + * @param func The function to run when the button is released, the function MUST NOT block + * @return true The listener was successfully registered + * @return false The listener was not successfully registered (there is already a listener with this name) + * + * @b Example: + * @code {.cpp} + * // Use a function... + * Gamepad::master.X.onRelease("stopFlywheel", stopFlywheel); + * // ...or a lambda + * Gamepad::master.Y.onRelease("stopIntake", []() { intake.move(0); }); + * @endcode + */ + bool onRelease(std::string listenerName, std::function func) const; + /** + * @brief Register a function to run when the button is short released. + * + * By default, shortRelease will fire when the button has been released before 500ms, this threshold can be + * adjusted by changing long_press_threshold. + * + * @note This event will most likely be used along with the longPress event. + * + * @param listenerName The name of the listener, this must be a unique name + * @param func The function to run when the button is short released, the function MUST NOT block + * @return true The listener was successfully registered + * @return false The listener was not successfully registered (there is already a listener with this name) + * + * @b Example: + * @code {.cpp} + * // Use a function... + * Gamepad::master.A.onShortRelease("raiseLiftOneLevel", raiseLiftOneLevel); + * // ...or a lambda + * Gamepad::master.B.onShortRelease("intakeOnePicce", []() { intake.move_relative(600, 100); }); + * @endcode + */ + bool onShortRelease(std::string listenerName, std::function func) const; + /** + * @brief Register a function to run for a given event. + * + * @param event Which event to register the listener on. + * @param listenerName The name of the listener, this must be a unique name + * @param func The function to run for the given event, the function MUST NOT block + * @return true The listener was successfully registered + * @return false The listener was not successfully registered (there is already a listener with this name) + * + * @b Example: + * @code {.cpp} + * // Use a function... + * Gamepad::master.L1.addListener(Gamepad::ON_PRESS, "start_spin", startSpin); + * // ...or a lambda + * Gamepad::master.L1.addListener(Gamepad::ON_RELEASE, "stop_spin", []() { motor1.brake(); }); + * @endcode + */ + bool addListener(EventType event, std::string listenerName, std::function func) const; + /** + * @brief + * + * @param event + * @param listenerName + * @return true + * @return false + */ + bool hasListener(EventType event, std::string listenerName) const; + /** + * @brief Removes a listener from the button + * @warning Usage of this function is discouraged. + * + * @param listenerName The name of the listener to remove + * @return true The specified listener was successfully removed + * @return false The specified listener could not be removed + * + * @b Example: + * @code {.cpp} + * // Add an event listener... + * Gamepad::master.L1.addListener(Gamepad::ON_PRESS, "do_something", doSomething); + * // ...and now get rid of it + * Gamepad::master.L1.removeListener("do_something"); + * @endcode + */ + bool removeListener(EventType event, std::string listenerName) const; + + /** + * @brief Returns a value indicating whether the button is currently being held. + * + * @return true The button is currently pressed + * @return false The button is not currently pressed + */ + explicit operator bool() const { return is_pressed; } + private: + /** + * @brief Updates the button and runs any event handlers, if necessary + * + * @param is_held Whether or not the button is currently held down + */ + void update(bool is_held); + /// he last time the update function was called + uint32_t last_update_time = pros::millis(); + /// The last time the long press event was fired + uint32_t last_long_press_time = 0; + mutable _impl::EventHandler onPressEvent {}; + mutable _impl::EventHandler onLongPressEvent {}; + mutable _impl::EventHandler onReleaseEvent {}; + mutable _impl::EventHandler onShortReleaseEvent {}; +}; +} // namespace Gamepad \ No newline at end of file diff --git a/include/gamepad/controller.hpp b/include/gamepad/controller.hpp index 5842604..363bd55 100644 --- a/include/gamepad/controller.hpp +++ b/include/gamepad/controller.hpp @@ -1,181 +1,16 @@ #pragma once #include "pros/misc.h" -#include -#include #include #ifndef PROS_USE_SIMPLE_NAMES #define PROS_USE_SIMPLE_NAMES #endif -#include "event_handler.hpp" +#include "button.hpp" #include "pros/misc.hpp" -#include "pros/rtos.hpp" namespace Gamepad { -enum EventType { - ON_PRESS, - ON_LONG_PRESS, - ON_RELEASE, - ON_SHORT_RELEASE, -}; - -class Button { - friend class Controller; - public: - /// Whether the button has just been pressed - bool rising_edge = false; - /// Whether the button has just been released - bool falling_edge = false; - /// Whether the button is currently held down - bool is_pressed = false; - /// How long the button has been held down - uint32_t time_held = 0; - /// How long the button has been released - uint32_t time_released = 0; - /// How long the threshold should be for the longPress and shortRelease events - uint32_t long_press_threshold = 500; - /** - * @brief Register a function to run when the button is pressed. - * - * @param listenerName The name of the listener, this must be a unique name - * @param func The function to run when the button is pressed, the function MUST NOT block - * @return true The listener was successfully registered - * @return false The listener was not successfully registered (there is already a listener with this name) - * - * @b Example: - * @code {.cpp} - * // Use a function... - * Gamepad::master.Down.onPress("downPress1", downPress1); - * // ...or a lambda - * Gamepad::master.Up.onPress("upPress1", []() { std::cout << "I was pressed!" << std::endl; }); - * @endcode - */ - bool onPress(std::string listenerName, std::function func) const; - /** - * @brief Register a function to run when the button is long pressed. - * - * By default, onLongPress will fire when the button has been held down for - * 500ms or more, this threshold can be adjusted by changing long_press_threshold. - * - * @warning When using this event along with onPress, both the onPress - * and onlongPress listeners may fire together. - * - * @param listenerName The name of the listener, this must be a unique name - * @param func The function to run when the button is long pressed, the function MUST NOT block - * @return true The listener was successfully registered - * @return false The listener was not successfully registered (there is already a listener with this name) - * - * @b Example: - * @code {.cpp} - * // Use a function... - * Gamepad::master.Left.onLongPress("fireCatapult", fireCatapult); - * // ...or a lambda - * Gamepad::master.Right.onLongPress("print_right", []() { std::cout << "Right button was long pressed!" << - * std::endl; }); - * @endcode - */ - bool onLongPress(std::string listenerName, std::function func) const; - /** - * @brief Register a function to run when the button is released. - * - * @param listenerName The name of the listener, this must be a unique name - * @param func The function to run when the button is released, the function MUST NOT block - * @return true The listener was successfully registered - * @return false The listener was not successfully registered (there is already a listener with this name) - * - * @b Example: - * @code {.cpp} - * // Use a function... - * Gamepad::master.X.onRelease("stopFlywheel", stopFlywheel); - * // ...or a lambda - * Gamepad::master.Y.onRelease("stopIntake", []() { intake.move(0); }); - * @endcode - */ - bool onRelease(std::string listenerName, std::function func) const; - /** - * @brief Register a function to run when the button is short released. - * - * By default, shortRelease will fire when the button has been released before 500ms, this threshold can be - * adjusted by changing long_press_threshold. - * - * @note This event will most likely be used along with the longPress event. - * - * @param listenerName The name of the listener, this must be a unique name - * @param func The function to run when the button is short released, the function MUST NOT block - * @return true The listener was successfully registered - * @return false The listener was not successfully registered (there is already a listener with this name) - * - * @b Example: - * @code {.cpp} - * // Use a function... - * Gamepad::master.A.onShortRelease("raiseLiftOneLevel", raiseLiftOneLevel); - * // ...or a lambda - * Gamepad::master.B.onShortRelease("intakeOnePicce", []() { intake.move_relative(600, 100); }); - * @endcode - */ - bool onShortRelease(std::string listenerName, std::function func) const; - /** - * @brief Register a function to run for a given event. - * - * @param event Which event to register the listener on. - * @param listenerName The name of the listener, this must be a unique name - * @param func The function to run for the given event, the function MUST NOT block - * @return true The listener was successfully registered - * @return false The listener was not successfully registered (there is already a listener with this name) - * - * @b Example: - * @code {.cpp} - * // Use a function... - * Gamepad::master.L1.addListener(Gamepad::ON_PRESS, "start_spin", startSpin); - * // ...or a lambda - * Gamepad::master.L1.addListener(Gamepad::ON_RELEASE, "stop_spin", []() { motor1.brake(); }); - * @endcode - */ - bool addListener(EventType event, std::string listenerName, std::function func) const; - /** - * @brief Removes a listener from the button - * @warning Usage of this function is discouraged. - * - * @param listenerName The name of the listener to remove - * @return true The specified listener was successfully removed - * @return false The specified listener could not be removed - * - * @b Example: - * @code {.cpp} - * // Add an event listener... - * Gamepad::master.L1.addListener(Gamepad::ON_PRESS, "do_something", doSomething); - * // ...and now get rid of it - * Gamepad::master.L1.removeListener("do_something"); - * @endcode - */ - bool removeListener(std::string listenerName) const; - - /** - * @brief Returns a value indicating whether the button is currently being held. - * - * @return true The button is currently pressed - * @return false The button is not currently pressed - */ - explicit operator bool() const { return is_pressed; } - private: - /** - * @brief Updates the button and runs any event handlers, if necessary - * - * @param is_held Whether or not the button is currently held down - */ - void update(bool is_held); - /// he last time the update function was called - uint32_t last_update_time = pros::millis(); - /// The last time the long press event was fired - uint32_t last_long_press_time = 0; - mutable _impl::EventHandler onPressEvent {}; - mutable _impl::EventHandler onLongPressEvent {}; - mutable _impl::EventHandler onReleaseEvent {}; - mutable _impl::EventHandler onShortReleaseEvent {}; -}; - class Controller { public: /** diff --git a/include/gamepad/event_handler.hpp b/include/gamepad/event_handler.hpp index 76ab6cc..8c72c76 100644 --- a/include/gamepad/event_handler.hpp +++ b/include/gamepad/event_handler.hpp @@ -53,6 +53,11 @@ template class EventHandler { return false; } + bool has_listener(Key key) { + auto i = std::find(keys.begin(), keys.end(), std::move(key)); + return i != keys.end(); + } + /** * @brief Whther or not there are any listeners registered * diff --git a/include/gamepad/recursive_mutex.hpp b/include/gamepad/recursive_mutex.hpp index d892f2c..40c76fa 100644 --- a/include/gamepad/recursive_mutex.hpp +++ b/include/gamepad/recursive_mutex.hpp @@ -1,5 +1,8 @@ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" // apix.h includes llemu headers (???) which trigger this warning #include "pros/apix.h" #include "pros/rtos.h" +#pragma GCC diagnostic pop namespace Gamepad::_impl { diff --git a/include/main.h b/include/main.h index 42e7817..a6e6dde 100644 --- a/include/main.h +++ b/include/main.h @@ -34,7 +34,10 @@ */ #define PROS_USE_LITERALS +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" // api.h includes llemu headers which trigger this warning #include "api.h" +#pragma GCC diagnostic pop /** * You should add more #includes here diff --git a/src/gamepad/bindings.cpp b/src/gamepad/bindings.cpp new file mode 100644 index 0000000..33d032f --- /dev/null +++ b/src/gamepad/bindings.cpp @@ -0,0 +1,53 @@ +#include + +#include "bindings.hpp" +#include "controller.hpp" + +namespace Gamepad::Bindings { +bool motorTwoButton(std::string name, pros::Motor motor, Button& fwd_button, Button& rev_button) { + enum states { FWD, REV, NONE }; + + bool ret_val = true; + ret_val &= !fwd_button.hasListener(EventType::ON_PRESS, "Bindings" + name); + ret_val &= !fwd_button.hasListener(EventType::ON_RELEASE, "Bindings" + name); + ret_val &= !rev_button.hasListener(EventType::ON_PRESS, "Bindings" + name); + ret_val &= !rev_button.hasListener(EventType::ON_RELEASE, "Bindings" + name); + if (!ret_val) return false; + + auto state = std::make_shared(NONE); + fwd_button.onPress("Bindings" + name, [=]() { + *state = FWD; + motor.move(127); + }); + fwd_button.onRelease("Bindings" + name, [=]() { + if (*state == FWD) { + *state = NONE; + motor.move(0); + } + }); + rev_button.onPress("Bindings" + name, [=]() { + *state = REV; + motor.move(-127); + }); + rev_button.onRelease("Bindings" + name, [=]() { + if (*state == REV) { + *state = NONE; + motor.move(0); + } + }); + return true; +} + +bool motorOneButton(std::string name, pros::Motor motor, Button& toggle_button, uint32_t speed) { + bool ret_val = true; + ret_val &= !toggle_button.hasListener(EventType::ON_PRESS, "Bindings" + name); + if (!ret_val) return false; + auto state = std::make_shared(false); + ret_val &= toggle_button.onPress("Bindings" + name, [=]() { + if (*state) motor.move(speed); + else motor.move(0); + *state = !*state; + }); + return true; +} +} // namespace Gamepad::Bindings \ No newline at end of file diff --git a/src/gamepad/button.cpp b/src/gamepad/button.cpp new file mode 100644 index 0000000..3815739 --- /dev/null +++ b/src/gamepad/button.cpp @@ -0,0 +1,85 @@ +#include "controller.hpp" +#include "todo.hpp" + +namespace Gamepad { + +bool Button::onPress(std::string listenerName, std::function func) const { + return this->onPressEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); +} + +bool Button::onLongPress(std::string listenerName, std::function func) const { + return this->onLongPressEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); +} + +bool Button::onRelease(std::string listenerName, std::function func) const { + return this->onReleaseEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); +} + +bool Button::onShortRelease(std::string listenerName, std::function func) const { + return this->onShortReleaseEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); +} + +bool Button::addListener(EventType event, std::string listenerName, std::function func) const { + switch (event) { + case Gamepad::EventType::ON_PRESS: return this->onPress(std::move(listenerName), std::move(func)); + case Gamepad::EventType::ON_LONG_PRESS: return this->onLongPress(std::move(listenerName), std::move(func)); + case Gamepad::EventType::ON_RELEASE: return this->onRelease(std::move(listenerName), std::move(func)); + case Gamepad::EventType::ON_SHORT_RELEASE: + return this->onShortRelease(std::move(listenerName), std::move(func)); + default: + TODO("add error logging") + errno = EINVAL; + return false; + } +} + +bool Button::removeListener(EventType event, std::string listenerName) const { + switch (event) { + case Gamepad::EventType::ON_PRESS: return this->onPressEvent.remove_listener(listenerName + "_user"); + case Gamepad::EventType::ON_LONG_PRESS: return this->onLongPressEvent.remove_listener(listenerName + "_user"); + case Gamepad::EventType::ON_RELEASE: return this->onReleaseEvent.remove_listener(listenerName + "_user"); + case Gamepad::EventType::ON_SHORT_RELEASE: + return this->onShortReleaseEvent.remove_listener(listenerName + "_user"); + default: + TODO("add error logging") + errno = EINVAL; + return false; + } +} + +bool Button::hasListener(EventType event, std::string listenerName) const { + switch (event) { + case Gamepad::EventType::ON_PRESS: return this->onPressEvent.has_listener(listenerName + "_user"); + case Gamepad::EventType::ON_LONG_PRESS: return this->onLongPressEvent.has_listener(listenerName + "_user"); + case Gamepad::EventType::ON_RELEASE: return this->onReleaseEvent.has_listener(listenerName + "_user"); + case Gamepad::EventType::ON_SHORT_RELEASE: + return this->onShortReleaseEvent.has_listener(listenerName + "_user"); + default: + TODO("add error logging") + errno = EINVAL; + return false; + } +} + +void Button::update(const bool is_held) { + this->rising_edge = !this->is_pressed && is_held; + this->falling_edge = this->is_pressed && !is_held; + this->is_pressed = is_held; + if (is_held) this->time_held += pros::millis() - this->last_update_time; + else this->time_released += pros::millis() - this->last_update_time; + + if (this->rising_edge) { + this->onPressEvent.fire(); + } else if (this->is_pressed && this->time_held >= this->long_press_threshold && + this->last_long_press_time <= pros::millis() - this->time_held) { + this->onLongPressEvent.fire(); + this->last_long_press_time = pros::millis(); + } else if (this->falling_edge) { + this->onReleaseEvent.fire(); + if (this->time_held < this->long_press_threshold) this->onShortReleaseEvent.fire(); + } + if (this->rising_edge) this->time_held = 0; + if (this->falling_edge) this->time_released = 0; + this->last_update_time = pros::millis(); +} +} // namespace Gamepad \ No newline at end of file diff --git a/src/gamepad/controller.cpp b/src/gamepad/controller.cpp index 720c1f4..c89b027 100644 --- a/src/gamepad/controller.cpp +++ b/src/gamepad/controller.cpp @@ -4,64 +4,6 @@ #include namespace Gamepad { -bool Button::onPress(std::string listenerName, std::function func) const { - return this->onPressEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); -} - -bool Button::onLongPress(std::string listenerName, std::function func) const { - return this->onLongPressEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); -} - -bool Button::onRelease(std::string listenerName, std::function func) const { - return this->onReleaseEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); -} - -bool Button::onShortRelease(std::string listenerName, std::function func) const { - return this->onShortReleaseEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); -} - -bool Button::addListener(EventType event, std::string listenerName, std::function func) const { - switch (event) { - case Gamepad::EventType::ON_PRESS: return this->onPress(std::move(listenerName), std::move(func)); - case Gamepad::EventType::ON_LONG_PRESS: return this->onLongPress(std::move(listenerName), std::move(func)); - case Gamepad::EventType::ON_RELEASE: return this->onRelease(std::move(listenerName), std::move(func)); - case Gamepad::EventType::ON_SHORT_RELEASE: - return this->onShortRelease(std::move(listenerName), std::move(func)); - default: - TODO("add error logging") - errno = EINVAL; - return false; - } -} - -bool Button::removeListener(std::string listenerName) const { - return this->onPressEvent.remove_listener(listenerName + "_user") || - this->onLongPressEvent.remove_listener(listenerName + "_user") || - this->onReleaseEvent.remove_listener(listenerName + "_user") || - this->onShortReleaseEvent.remove_listener(listenerName + "_user"); -} - -void Button::update(const bool is_held) { - this->rising_edge = !this->is_pressed && is_held; - this->falling_edge = this->is_pressed && !is_held; - this->is_pressed = is_held; - if (is_held) this->time_held += pros::millis() - this->last_update_time; - else this->time_released += pros::millis() - this->last_update_time; - - if (this->rising_edge) { - this->onPressEvent.fire(); - } else if (this->is_pressed && this->time_held >= this->long_press_threshold && - this->last_long_press_time <= pros::millis() - this->time_held) { - this->onLongPressEvent.fire(); - this->last_long_press_time = pros::millis(); - } else if (this->falling_edge) { - this->onReleaseEvent.fire(); - if (this->time_held < this->long_press_threshold) this->onShortReleaseEvent.fire(); - } - if (this->rising_edge) this->time_held = 0; - if (this->falling_edge) this->time_released = 0; - this->last_update_time = pros::millis(); -} void Controller::updateButton(pros::controller_digital_e_t button_id) { Button Controller::*button = Controller::button_to_ptr(button_id);