diff --git a/.github/workflows/pros-build-release.yml b/.github/workflows/pros-build-release.yml new file mode 100644 index 0000000..301a532 --- /dev/null +++ b/.github/workflows/pros-build-release.yml @@ -0,0 +1,24 @@ +name: Build Release + +on: + release: + types: [published] + +jobs: + build-release: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + - name: Build Template + id: build_step + uses: LemLib/pros-build@v2.0.2 + with: + lib_folder_name: "gamepad" + copy_readme_and_license_to_include: true + no_commit_hash: true + - name: Upload Template To Release + uses: svenstaro/upload-release-action@v2 + with: + file: ${{ github.workspace }}/${{ steps.build_step.outputs.name }} diff --git a/.github/workflows/pros-build.yml b/.github/workflows/pros-build.yml index 88566b5..d34f97d 100644 --- a/.github/workflows/pros-build.yml +++ b/.github/workflows/pros-build.yml @@ -12,7 +12,16 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: LemLib/pros-build@v2.0.2 + - name: Checkout Code + uses: actions/checkout@v4 + - name: Build Template + id: build_step + uses: LemLib/pros-build@v2.0.2 with: - library-path: gamepad + lib_folder_name: "gamepad" + copy_readme_and_license_to_include: true + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.build_step.outputs.name }} + path: ${{ github.workspace }}/template/* diff --git a/Makefile b/Makefile index 10d24a2..d97a45b 100644 --- a/Makefile +++ b/Makefile @@ -25,9 +25,8 @@ 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 +VERSION:=0.2.0 # EXCLUDE_SRC_FROM_LIB= $(SRCDIR)/unpublishedfile.c # this line excludes opcontrol.c and similar files EXCLUDE_SRC_FROM_LIB+=$(foreach file, $(SRCDIR)/main,$(foreach cext,$(CEXTS),$(file).$(cext)) $(foreach cxxext,$(CXXEXTS),$(file).$(cxxext))) diff --git a/include/gamepad/button.hpp b/include/gamepad/button.hpp index f1126d3..b867da7 100644 --- a/include/gamepad/button.hpp +++ b/include/gamepad/button.hpp @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include "event_handler.hpp" @@ -10,6 +12,8 @@ enum EventType { ON_LONG_PRESS, ON_RELEASE, ON_SHORT_RELEASE, + ON_LONG_RELEASE, + ON_REPEAT_PRESS, }; class Button { @@ -25,8 +29,45 @@ class Button { 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; + /// How many times the button has been repeat-pressed + uint32_t repeat_iterations = 0; + /** + * @brief Set the time for a press to be considered a long press for the button + * + * @note this is likely to be used with the onLongPress(), onShortRelease(), onLongRelease(), or onRepeatPress() + * events + * + * @param threshold the time in ms that would be considered a long press + * + * @b Example: + * @code {.cpp} + * // change the threshold + * gamepad::master.Left.set_long_press_threshold(5000); + * // then call the function + * gamepad::master.Left.onLongPress("longPress1", []() { + * std::cout << "I was held for 5000ms instead of the 500ms default!" << std::endl; + * }); + * @endcode + */ + void set_long_press_threshold(uint32_t threshold) const; + /** + * @brief Set the interval for the repeatPress event to repeat + * + * @note this is likely to be used with the onRepeatPress() event + * + * @param cooldown the interval in ms + * + * @b Example: + * @code {.cpp} + * // change the threshold + * gamepad::master.Up.set_repeat_cooldown(100); + * // then call the function + * gamepad::master.Up.onRepeatPress("repeatPress1", []() { + * std::cout << "I'm being repeated every 100ms instead of the 50ms default!" << std::endl; + * }); + * @endcode + */ + void set_repeat_cooldown(uint32_t cooldown) const; /** * @brief Register a function to run when the button is pressed. * @@ -48,7 +89,7 @@ class Button { * @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. + * 500ms or more, this threshold can be adjusted via the set_long_press_threshold() method. * * @warning When using this event along with onPress, both the onPress * and onlongPress listeners may fire together. @@ -89,7 +130,7 @@ class Button { * @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. + * adjusted via the set_long_press_threshold() method. * * @note This event will most likely be used along with the longPress event. * @@ -103,10 +144,52 @@ class Button { * // Use a function... * gamepad::master.A.onShortRelease("raiseLiftOneLevel", raiseLiftOneLevel); * // ...or a lambda - * gamepad::master.B.onShortRelease("intakeOnePicce", []() { intake.move_relative(600, 100); }); + * gamepad::master.B.onShortRelease("intakeOnePiece", []() { intake.move_relative(600, 100); }); * @endcode */ bool onShortRelease(std::string listenerName, std::function func) const; + /** + * @brief Register a function to run when the button is long released. + * + * By default, longRelease will fire when the button has been released after 500ms, this threshold can be + * adjusted via the set_long_press_threshold() method. + * + * @param listenerName The name of the listener, this must be a unique name + * @param func The function to run when the button is long 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.Up.onLongRelease("moveLiftToGround", moveLiftToGround); + * // ...or a lambda + * gamepad::master.Left.onLongRelease("spinIntake", []() { intake.move(127); }); + * @endcode + * + */ + bool onLongRelease(std::string listenerName, std::function func) const; + /** + * @brief Register a function to run periodically after its been held + * + * By default repeatPress will start repeating after 500ms and repeat every 50ms, this can be adjusted via the + * set_long_press_threshold() and set_repeat_cooldown() methods respectively + * + * @param listenerName The name of the listener, this must be a unique name + * @param func the function to run periodically when the button is held, 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.onRepeatPress("shootDisk", shootOneDisk); + * // ...or a lambda + * gamepad::master.A.onRepeatPress("scoreOneRing", []() { intake.move_relative(200, 100); }); + * @endcode + * + */ + bool onRepeatPress(std::string listenerName, std::function func) const; /** * @brief Register a function to run for a given event. * @@ -157,13 +240,21 @@ class Button { * @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 + /// How long the threshold should be for the longPress and shortRelease events + mutable uint32_t long_press_threshold = 500; + /// How often repeatPress is called + mutable uint32_t repeat_cooldown = 50; + /// The 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; + /// The last time the repeat event was called + uint32_t last_repeat_time = 0; mutable _impl::EventHandler onPressEvent {}; mutable _impl::EventHandler onLongPressEvent {}; mutable _impl::EventHandler onReleaseEvent {}; mutable _impl::EventHandler onShortReleaseEvent {}; + mutable _impl::EventHandler onLongReleaseEvent {}; + mutable _impl::EventHandler onRepeatPressEvent {}; }; } // namespace gamepad \ No newline at end of file diff --git a/src/gamepad/button.cpp b/src/gamepad/button.cpp index 74dfc66..703c246 100644 --- a/src/gamepad/button.cpp +++ b/src/gamepad/button.cpp @@ -1,8 +1,14 @@ #include "gamepad/button.hpp" #include "gamepad/todo.hpp" #include "pros/rtos.hpp" +#include +#include namespace gamepad { +void Button::set_long_press_threshold(uint32_t threshold) const { this->long_press_threshold = threshold; } + +void Button::set_repeat_cooldown(uint32_t cooldown) const { this->repeat_cooldown = cooldown; } + bool Button::onPress(std::string listenerName, std::function func) const { return this->onPressEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); } @@ -19,6 +25,14 @@ bool Button::onShortRelease(std::string listenerName, std::function return this->onShortReleaseEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); } +bool Button::onLongRelease(std::string listenerName, std::function func) const { + return this->onLongReleaseEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); +} + +bool Button::onRepeatPress(std::string listenerName, std::function func) const { + return this->onRepeatPressEvent.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)); @@ -26,6 +40,8 @@ bool Button::addListener(EventType event, std::string listenerName, std::functio 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)); + case gamepad::EventType::ON_LONG_RELEASE: return this->onLongRelease(std::move(listenerName), std::move(func)); + case gamepad::EventType::ON_REPEAT_PRESS: return this->onRepeatPress(std::move(listenerName), std::move(func)); default: TODO("add error logging") errno = EINVAL; @@ -37,7 +53,9 @@ 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"); + this->onShortReleaseEvent.remove_listener(listenerName + "_user") || + this->onLongReleaseEvent.remove_listener(listenerName + "_user") || + this->onRepeatPressEvent.remove_listener(listenerName + "_user"); } void Button::update(const bool is_held) { @@ -53,10 +71,19 @@ void Button::update(const bool is_held) { this->last_long_press_time <= pros::millis() - this->time_held) { this->onLongPressEvent.fire(); this->last_long_press_time = pros::millis(); + this->last_repeat_time = pros::millis() - this->repeat_cooldown; + this->repeat_iterations = 0; + } else if (this->is_pressed && this->time_held >= this->long_press_threshold && + pros::millis() - this->last_repeat_time >= this->repeat_cooldown) { + this->repeat_iterations++; + this->onRepeatPressEvent.fire(); + this->last_repeat_time = pros::millis(); } else if (this->falling_edge) { this->onReleaseEvent.fire(); if (this->time_held < this->long_press_threshold) this->onShortReleaseEvent.fire(); + else this->onLongReleaseEvent.fire(); } + if (this->rising_edge) this->time_held = 0; if (this->falling_edge) this->time_released = 0; this->last_update_time = pros::millis();