lib/kudzu_buttons: imgui, null and pi4ioe5v6416 backends
Also update badge/main.cc with button controls for:
- dpad: KUDZU sprite wave offsets
- start: reset wave to default
- B: toggle background color animation
- A: toggle nametag display
Change-Id: I27d52074c91c07edf49fdf895b1974be0a72ca4a
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/kudzu/+/193210
Reviewed-by: Kayce Basques <kayce@google.com>
Commit-Queue: Anthony DiGirolamo <tonymd@google.com>
diff --git a/applications/app_common/BUILD.gn b/applications/app_common/BUILD.gn
index 64bfca0..e5d5d60 100644
--- a/applications/app_common/BUILD.gn
+++ b/applications/app_common/BUILD.gn
@@ -30,6 +30,7 @@
"$dir_pw_display",
"$dir_pw_status",
"$dir_pw_thread:thread",
+ "//lib/kudzu_buttons",
"//lib/kudzu_imu",
"//lib/pw_touchscreen",
]
diff --git a/applications/app_common/public/app_common/common.h b/applications/app_common/public/app_common/common.h
index 6596189..e9c568f 100644
--- a/applications/app_common/public/app_common/common.h
+++ b/applications/app_common/public/app_common/common.h
@@ -13,6 +13,7 @@
// the License.
#pragma once
+#include "kudzu_buttons/buttons.h"
#include "kudzu_imu/imu.h"
#include "pw_display/display.h"
#include "pw_status/status.h"
@@ -40,6 +41,8 @@
// Return an initialized touchscreen.
static pw::touchscreen::Touchscreen& GetTouchscreen();
+ static kudzu::Buttons& GetButtons();
+
// Provides thread options for the display thread.
static const pw::thread::Options& DisplayDrawThreadOptions();
diff --git a/applications/app_common_impl/BUILD.gn b/applications/app_common_impl/BUILD.gn
index 3d53560..ab29dba 100644
--- a/applications/app_common_impl/BUILD.gn
+++ b/applications/app_common_impl/BUILD.gn
@@ -108,6 +108,7 @@
deps += [
"$dir_pw_display_driver_st7789",
"$dir_pw_pixel_pusher_rp2040_pio",
+ "//lib/kudzu_buttons_pi4ioe5v6416",
"//lib/pw_touchscreen_ft6236",
]
sources = [ "common_pico.cc" ]
@@ -123,6 +124,7 @@
"$dir_pw_thread:thread",
"$dir_pw_thread_stl:thread",
"//applications/app_common:app_common.facade",
+ "//lib/kudzu_buttons_imgui",
"//lib/kudzu_imu_imgui",
"//lib/pw_touchscreen_imgui",
]
@@ -141,6 +143,7 @@
"$dir_pw_thread:thread",
"$dir_pw_thread_stl:thread",
"//applications/app_common:app_common.facade",
+ "//lib/kudzu_buttons_null",
"//lib/pw_touchscreen_null",
]
sources = [ "common_host_null.cc" ]
diff --git a/applications/app_common_impl/common_host_imgui.cc b/applications/app_common_impl/common_host_imgui.cc
index b9d3675..c9e02e2 100644
--- a/applications/app_common_impl/common_host_imgui.cc
+++ b/applications/app_common_impl/common_host_imgui.cc
@@ -12,11 +12,13 @@
// License for the specific language governing permissions and limitations under
// the License.
#include "app_common/common.h"
+#include "kudzu_buttons_imgui/buttons.h"
#include "kudzu_imu_imgui/imu.h"
#include "pw_color/color.h"
#include "pw_display_driver_imgui/display_driver.h"
#include "pw_display_imgui/display.h"
#include "pw_framebuffer_pool/framebuffer_pool.h"
+#include "pw_status/status.h"
#include "pw_status/try.h"
#include "pw_thread/thread.h"
#include "pw_thread_stl/options.h"
@@ -28,6 +30,7 @@
using pw::framebuffer_pool::FramebufferPool;
using Touchscreen = pw::touchscreen::TouchscreenImGui;
+using Buttons = kudzu::ButtonsImgui;
namespace {
@@ -58,7 +61,17 @@
// static
Status Common::EndOfFrameCallback() { return pw::OkStatus(); }
-Status Common::Init() { return s_display_driver.Init(); }
+Status Common::Init() {
+ auto status = s_display_driver.Init();
+ if (!status.ok()) {
+ return status;
+ }
+ status = GetButtons().Init();
+ if (!status.ok()) {
+ return status;
+ }
+ return pw::OkStatus();
+}
// static
pw::display::Display& Common::GetDisplay() {
@@ -72,6 +85,11 @@
return s_touchscreen;
}
+kudzu::Buttons& Common::GetButtons() {
+ static Buttons s_buttons = Buttons(s_display_driver);
+ return s_buttons;
+}
+
kudzu::imu::PollingImu& Common::GetImu() {
static kudzu::imu::PollingImuImGui s_imu = kudzu::imu::PollingImuImGui();
return s_imu;
diff --git a/applications/app_common_impl/common_host_null.cc b/applications/app_common_impl/common_host_null.cc
index 11c9011..283c1e7 100644
--- a/applications/app_common_impl/common_host_null.cc
+++ b/applications/app_common_impl/common_host_null.cc
@@ -12,6 +12,7 @@
// License for the specific language governing permissions and limitations under
// the License.
#include "app_common/common.h"
+#include "kudzu_buttons_null/buttons.h"
#include "pw_display/display.h"
#include "pw_display_driver_null/display_driver.h"
#include "pw_status/try.h"
@@ -23,6 +24,8 @@
using pw::framebuffer::PixelFormat;
using pw::framebuffer_pool::FramebufferPool;
+using Buttons = kudzu::ButtonsNull;
+
namespace {
constexpr pw::math::Size<uint16_t> kDisplaySize = {DISPLAY_WIDTH,
@@ -57,6 +60,11 @@
return s_touchscreen;
}
+kudzu::Buttons& Common::GetButtons() {
+ static Buttons s_buttons = Buttons();
+ return s_buttons;
+}
+
const pw::thread::Options& Common::DisplayDrawThreadOptions() {
static pw::thread::stl::Options display_draw_thread_options;
return display_draw_thread_options;
@@ -65,4 +73,4 @@
const pw::thread::Options& Common::TouchscreenThreadOptions() {
static pw::thread::stl::Options display_draw_thread_options;
return display_draw_thread_options;
-}
\ No newline at end of file
+}
diff --git a/applications/app_common_impl/common_pico.cc b/applications/app_common_impl/common_pico.cc
index 5701c02..c3d6cf1 100644
--- a/applications/app_common_impl/common_pico.cc
+++ b/applications/app_common_impl/common_pico.cc
@@ -24,6 +24,7 @@
#include "hardware/pwm.h"
#include "hardware/vreg.h"
#include "icm42670p/device.h"
+#include "kudzu_buttons_pi4ioe5v6416/buttons.h"
#include "kudzu_imu_icm42670p/imu.h"
#include "max17048/device.h"
#include "pi4ioe5v6416/device.h"
@@ -60,6 +61,7 @@
#endif
using Touchscreen = pw::touchscreen::TouchscreenFT6236;
+using Buttons = kudzu::ButtonsPI4IOE5V6416;
using pw::Status;
using pw::digital_io::Rp2040Config;
@@ -277,7 +279,7 @@
} // namespace
Status Common::EndOfFrameCallback() {
- // touch_screen_controller.LogControllerInfo();
+ touch_screen_controller.LogControllerInfo();
if (io_expander.Probe() == pw::OkStatus()) {
io_expander.LogControllerInfo();
@@ -383,6 +385,11 @@
return s_touchscreen;
}
+kudzu::Buttons& Common::GetButtons() {
+ static Buttons s_buttons = Buttons(&io_expander);
+ return s_buttons;
+}
+
kudzu::imu::PollingImu& Common::GetImu() {
static kudzu::imu::PollingImuICM42670P s_imu(&imu);
return s_imu;
diff --git a/applications/badge/main.cc b/applications/badge/main.cc
index 669a8a8..ed48f08 100644
--- a/applications/badge/main.cc
+++ b/applications/badge/main.cc
@@ -22,6 +22,7 @@
#include "graphics/surface.hpp"
#include "heart_8x8.h"
#include "hello_my_name_is65x42.h"
+#include "kudzu_buttons/buttons.h"
#include "kudzu_isometric_text_sprite.h"
#include "libkudzu/framecounter.h"
#include "libkudzu/random.h"
@@ -46,6 +47,7 @@
#include "pw_thread/detached_thread.h"
#include "pw_touchscreen/touchscreen.h"
+using kudzu::Buttons;
using pw::color::color_rgb565_t;
using pw::color::colors_pico8_rgb565;
using pw::display::Display;
@@ -62,6 +64,7 @@
float angle = 0;
bool show_nametag = false;
+bool show_background = false;
// Draw the a waving text banner.
// Returns the bottom Y coordinate of the bottommost pixel set.
@@ -123,13 +126,15 @@
}
}
-void DrawKudzu(Framebuffer& framebuffer) {
+void DrawKudzu(Framebuffer& framebuffer,
+ float y_scale_offset = 0.0,
+ float x_scale_offset = 0.0) {
Vector2<int> tl = {0, 16};
const float y_scale = 12.0;
const float x_scale = 1.0;
- const float max_x_offset = 4.0;
- const float max_y_offset = 16.0;
+ const float max_x_offset = 4.0 + x_scale_offset;
+ const float max_y_offset = 16.0 + y_scale_offset;
// X offsets between each letter
std::array<int, 5> x_offsets = {32, 22, 26, 30, 0};
@@ -205,7 +210,6 @@
tag_position += blit::Point(4, name_rect_y_offset);
blit::Size name_size(screen.bounds.w - 8,
screen.bounds.h - name_rect_y_offset - 4);
- PW_LOG_DEBUG("Tag size: %d, %d", name_size.w, name_size.h);
blit::Rect name_rect(tag_position, name_size);
screen.pen = blit::Pen(0xff, 0xff, 0xff);
@@ -215,6 +219,19 @@
framebuffer, tag_position.x, tag_position.y, &name_tag_sprite_sheet, 1);
}
+void DrawBackgroundColors(Framebuffer& framebuffer) {
+ static color_rgb565_t base_color = 0;
+ static uint16_t magic = 27;
+
+ uint16_t* p = static_cast<uint16_t*>(framebuffer.data());
+ for (int y = 0; y < framebuffer.size().height; y++) {
+ for (int x = 0; x < framebuffer.size().width; x++) {
+ *p++ = base_color + magic * (x ^ y);
+ }
+ }
+ base_color += 0x0021;
+}
+
void MainTask(void*) {
kudzu::FrameCounter frame_counter = kudzu::FrameCounter();
@@ -237,7 +254,14 @@
Touchscreen& touchscreen = Common::GetTouchscreen();
pw::touchscreen::TouchEvent last_touch_event;
+ Buttons& kudzu_buttons = Common::GetButtons();
+
uint32_t frame_start_millis = pw::spin_delay::Millis();
+
+ float x_scale_offset = 0.0;
+ float y_scale_offset = 0.0;
+ const float x_scale_increment = 0.7;
+ const float y_scale_increment = 0.7;
// The display loop.
while (1) {
frame_counter.StartFrame();
@@ -263,6 +287,28 @@
blit::Point button_position(screen.bounds.w - button_size.w, 0);
blit::Rect mode_button_rect(button_position, button_size);
+ kudzu_buttons.Update();
+ if (kudzu_buttons.Pressed(kudzu::button::a)) {
+ show_nametag = !show_nametag;
+ }
+ if (kudzu_buttons.Pressed(kudzu::button::b)) {
+ show_background = !show_background;
+ }
+ if (kudzu_buttons.Pressed(kudzu::button::start)) {
+ x_scale_offset = 0;
+ y_scale_offset = 0;
+ }
+ if (kudzu_buttons.Held(kudzu::button::left)) {
+ y_scale_offset += y_scale_increment;
+ } else if (kudzu_buttons.Held(kudzu::button::right)) {
+ y_scale_offset -= y_scale_increment;
+ }
+ if (kudzu_buttons.Held(kudzu::button::up)) {
+ x_scale_offset += x_scale_increment;
+ } else if (kudzu_buttons.Held(kudzu::button::down)) {
+ x_scale_offset -= x_scale_increment;
+ }
+
if (show_nametag) {
DrawNametag(framebuffer, screen);
// Draw button
@@ -273,7 +319,10 @@
true,
blit::TextAlign::top_right);
} else {
- DrawKudzu(framebuffer);
+ if (show_background) {
+ DrawBackgroundColors(framebuffer);
+ }
+ DrawKudzu(framebuffer, x_scale_offset, y_scale_offset);
DrawGreeting(framebuffer, screen);
// Draw button
diff --git a/lib/kudzu_buttons/BUILD.gn b/lib/kudzu_buttons/BUILD.gn
new file mode 100644
index 0000000..d5482a3
--- /dev/null
+++ b/lib/kudzu_buttons/BUILD.gn
@@ -0,0 +1,33 @@
+# Copyright 2024 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+
+config("public_includes") {
+ include_dirs = [ "public" ]
+}
+
+pw_source_set("kudzu_buttons") {
+ public_configs = [ ":public_includes" ]
+ public = [ "public/kudzu_buttons/buttons.h" ]
+ public_deps = [
+ "$dir_pw_chrono:system_clock",
+ "$dir_pw_result",
+ "$dir_pw_status",
+ ]
+ deps = [ "$dir_pw_log" ]
+ sources = [ "buttons.cc" ]
+}
diff --git a/lib/kudzu_buttons/buttons.cc b/lib/kudzu_buttons/buttons.cc
new file mode 100644
index 0000000..6740bc7
--- /dev/null
+++ b/lib/kudzu_buttons/buttons.cc
@@ -0,0 +1,64 @@
+// Copyright 2024 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "kudzu_buttons/buttons.h"
+
+#include <chrono>
+#include <cstdint>
+
+#define PW_LOG_MODULE_NAME "kudzu_buttons"
+#define PW_LOG_LEVEL PW_LOG_LEVEL_INFO
+
+#include "pw_log/log.h"
+
+namespace kudzu {
+
+pw::Status Buttons::Update() {
+ // Get the new button bits.
+ pw::Result<std::bitset<kButtonCount>> update_result = DoUpdate();
+ if (!update_result.ok()) {
+ return update_result.status();
+ }
+
+ update_time_previous_ = update_time_;
+ update_time_ = pw::chrono::SystemClock::now();
+ pw::chrono::SystemClock::duration update_delta =
+ update_time_ - update_time_previous_;
+
+ button_bits_previous_ = button_bits_;
+ button_bits_ = update_result.value();
+
+ // Log if buttons changed.
+ if (button_bits_previous_ != button_bits_) {
+ std::string button_str = button_bits_.to_string();
+ PW_LOG_DEBUG("Buttons: %s", button_str.data());
+ }
+
+ // Track how long each button has been held.
+ for (int i = 0; i < kButtonCount; i++) {
+ if (Pressed(kudzu::button::ButtonName(i))) {
+ button_hold_duration_[i] = pw::chrono::SystemClock::duration(0);
+ PW_LOG_DEBUG("Button %d pressed", i);
+ } else if (Released(kudzu::button::ButtonName(i))) {
+ button_hold_duration_[i] = pw::chrono::SystemClock::duration(0);
+ PW_LOG_DEBUG("Button %d released", i);
+ } else if (Held(kudzu::button::ButtonName(i))) {
+ button_hold_duration_[i] += update_delta;
+ }
+ }
+
+ return pw::OkStatus();
+};
+
+} // namespace kudzu
diff --git a/lib/kudzu_buttons/public/kudzu_buttons/buttons.h b/lib/kudzu_buttons/public/kudzu_buttons/buttons.h
new file mode 100644
index 0000000..463f7d9
--- /dev/null
+++ b/lib/kudzu_buttons/public/kudzu_buttons/buttons.h
@@ -0,0 +1,96 @@
+// Copyright 2024 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include <bitset>
+#include <chrono>
+#include <cstdint>
+
+#include "pw_chrono/system_clock.h"
+#include "pw_result/result.h"
+#include "pw_status/status.h"
+
+namespace kudzu::button {
+
+enum ButtonName {
+ up = 0,
+ right = 1,
+ left = 2,
+ down = 3,
+ select = 4,
+ start = 5,
+ b = 6,
+ a = 7,
+};
+
+} // namespace kudzu::button
+
+namespace kudzu {
+
+constexpr int kButtonCount = 8;
+
+class Buttons {
+ public:
+ virtual ~Buttons() = default;
+ virtual pw::Status Init() = 0;
+ /// Fetch the latest button states and update held times.
+ pw::Status Update();
+
+ /// Function to be implemented that returns a bitset of length kButtonCount. A
+ /// button press corresponds to a bit == 1 and a release == 0.
+ virtual pw::Result<std::bitset<kButtonCount>> DoUpdate() = 0;
+
+ /// Returns true if the button was just pressed. This only fires once on the
+ /// transition from not pressed to pressed.
+ inline bool Pressed(kudzu::button::ButtonName button_name) {
+ return button_bits_[button_name] && !button_bits_previous_[button_name];
+ }
+
+ /// Returns true if the button was just released. This only fires once on the
+ /// transition from held to released.
+ inline bool Released(kudzu::button::ButtonName button_name) {
+ return !button_bits_[button_name] && button_bits_previous_[button_name];
+ }
+
+ /// Returns true if the button is being held down.
+ inline bool Held(kudzu::button::ButtonName button_name) {
+ return button_bits_[button_name] && button_bits_previous_[button_name];
+ }
+
+ /// Button hold duration accessor.
+ ///
+ /// @code
+ /// #include "pw_chrono/system_clock.h"
+ /// using namespace std::chrono_literals;
+ ///
+ /// if (buttons.HeldDuration(kudzu::button::up) >
+ /// pw::chrono::SystemClock::for_at_least(1000ms)) {
+ /// PW_LOG_INFO("Up button held for one second.");
+ /// }
+ /// @endcode
+ inline pw::chrono::SystemClock::duration HeldDuration(
+ kudzu::button::ButtonName button_name) {
+ return button_hold_duration_[button_name];
+ }
+
+ private:
+ pw::chrono::SystemClock::time_point update_time_previous_;
+ pw::chrono::SystemClock::time_point update_time_;
+ std::bitset<kButtonCount> button_bits_;
+ std::bitset<kButtonCount> button_bits_previous_;
+ std::array<pw::chrono::SystemClock::duration, kButtonCount>
+ button_hold_duration_;
+};
+
+} // namespace kudzu
diff --git a/lib/kudzu_buttons_imgui/BUILD.gn b/lib/kudzu_buttons_imgui/BUILD.gn
new file mode 100644
index 0000000..143ee23
--- /dev/null
+++ b/lib/kudzu_buttons_imgui/BUILD.gn
@@ -0,0 +1,38 @@
+# Copyright 2024 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+
+config("default_config") {
+ include_dirs = [ "public" ]
+}
+
+pw_source_set("kudzu_buttons_imgui") {
+ public_configs = [ ":default_config" ]
+ public = [ "public/kudzu_buttons_imgui/buttons.h" ]
+ deps = [
+ "$dir_pigweed_experimental/third_party/glfw",
+ "$dir_pigweed_experimental/third_party/imgui",
+ "$dir_pw_display_driver_imgui",
+ "$dir_pw_log",
+ "//lib/kudzu_buttons:kudzu_buttons",
+ ]
+ sources = [ "buttons.cc" ]
+ remove_configs = [ "$dir_pw_build:strict_warnings" ]
+ if (host_os == "linux") {
+ remove_configs += [ "$dir_pw_toolchain/host_clang:linux_sysroot" ]
+ }
+}
diff --git a/lib/kudzu_buttons_imgui/buttons.cc b/lib/kudzu_buttons_imgui/buttons.cc
new file mode 100644
index 0000000..8764e59
--- /dev/null
+++ b/lib/kudzu_buttons_imgui/buttons.cc
@@ -0,0 +1,83 @@
+// Copyright 2024 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "kudzu_buttons_imgui/buttons.h"
+
+#include "kudzu_buttons/buttons.h"
+
+#define PW_LOG_MODULE_NAME "kudzu_buttons_imgui"
+#define PW_LOG_LEVEL PW_LOG_LEVEL_INFO
+
+#include "pw_display_driver_imgui/display_driver.h"
+#include "pw_log/log.h"
+#include "pw_status/status.h"
+
+namespace kudzu {
+
+namespace {
+
+std::bitset<kButtonCount> current_buttons;
+
+// GLFW keyboard event handler.
+// See also: https://www.glfw.org/docs/3.3/input_guide.html#input_key
+void key_callback(
+ GLFWwindow* window, int key, int scancode, int action, int mods) {
+ bool state;
+ if (action == GLFW_PRESS) {
+ state = true;
+ } else if (action == GLFW_RELEASE) {
+ state = false;
+ } else {
+ // Don't handle actions other than press or release.
+ return;
+ }
+
+ // GLFW Keycodes: https://www.glfw.org/docs/3.3/group__keys.html
+ if (key == GLFW_KEY_W || key == GLFW_KEY_UP) {
+ current_buttons[kudzu::button::up] = state;
+ } else if (key == GLFW_KEY_A || key == GLFW_KEY_LEFT) {
+ current_buttons[kudzu::button::left] = state;
+ } else if (key == GLFW_KEY_S || key == GLFW_KEY_DOWN) {
+ current_buttons[kudzu::button::down] = state;
+ } else if (key == GLFW_KEY_D || key == GLFW_KEY_RIGHT) {
+ current_buttons[kudzu::button::right] = state;
+ } else if (key == GLFW_KEY_ENTER || key == GLFW_KEY_C) {
+ current_buttons[kudzu::button::start] = state;
+ } else if (key == GLFW_KEY_TAB || key == GLFW_KEY_V) {
+ current_buttons[kudzu::button::select] = state;
+ } else if (key == GLFW_KEY_COMMA || key == GLFW_KEY_Z) {
+ current_buttons[kudzu::button::b] = state;
+ } else if (key == GLFW_KEY_PERIOD || key == GLFW_KEY_X) {
+ current_buttons[kudzu::button::a] = state;
+ }
+}
+
+} // namespace
+
+ButtonsImgui::ButtonsImgui(
+ pw::display_driver::DisplayDriverImgUI& display_driver)
+ : display_driver_(display_driver) {}
+
+pw::Status ButtonsImgui::Init() {
+ glfwSetKeyCallback(display_driver_.GetGlfwWindow(), key_callback);
+ current_buttons.reset();
+
+ return pw::OkStatus();
+}
+
+pw::Result<std::bitset<kButtonCount>> ButtonsImgui::DoUpdate() {
+ return current_buttons;
+}
+
+} // namespace kudzu
diff --git a/lib/kudzu_buttons_imgui/public/kudzu_buttons_imgui/buttons.h b/lib/kudzu_buttons_imgui/public/kudzu_buttons_imgui/buttons.h
new file mode 100644
index 0000000..8f06fa6
--- /dev/null
+++ b/lib/kudzu_buttons_imgui/public/kudzu_buttons_imgui/buttons.h
@@ -0,0 +1,32 @@
+// Copyright 2024 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "kudzu_buttons/buttons.h"
+#include "pw_display_driver_imgui/display_driver.h"
+#include "pw_status/status.h"
+
+namespace kudzu {
+
+class ButtonsImgui : public Buttons {
+ public:
+ ButtonsImgui(pw::display_driver::DisplayDriverImgUI& display_driver);
+ pw::Status Init() override;
+ pw::Result<std::bitset<kButtonCount>> DoUpdate() override;
+
+ private:
+ pw::display_driver::DisplayDriverImgUI& display_driver_;
+};
+
+} // namespace kudzu
diff --git a/lib/kudzu_buttons_null/BUILD.gn b/lib/kudzu_buttons_null/BUILD.gn
new file mode 100644
index 0000000..cd81f2a
--- /dev/null
+++ b/lib/kudzu_buttons_null/BUILD.gn
@@ -0,0 +1,31 @@
+# Copyright 2024 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+
+config("default_config") {
+ include_dirs = [ "public" ]
+}
+
+pw_source_set("kudzu_buttons_null") {
+ public_configs = [ ":default_config" ]
+ public = [ "public/kudzu_buttons_null/buttons.h" ]
+ deps = [
+ "$dir_pw_log",
+ "//lib/kudzu_buttons:kudzu_buttons",
+ ]
+ sources = [ "buttons.cc" ]
+}
diff --git a/lib/kudzu_buttons_null/buttons.cc b/lib/kudzu_buttons_null/buttons.cc
new file mode 100644
index 0000000..97bb7b1
--- /dev/null
+++ b/lib/kudzu_buttons_null/buttons.cc
@@ -0,0 +1,30 @@
+// Copyright 2024 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "kudzu_buttons_null/buttons.h"
+
+#include "pw_status/status.h"
+
+namespace kudzu {
+
+ButtonsNull::ButtonsNull() {}
+
+pw::Status ButtonsNull::Init() { return pw::Status::Unimplemented(); }
+
+pw::Result<std::bitset<kButtonCount>> ButtonsNull::DoUpdate() {
+ std::bitset<kButtonCount> new_bits = 0;
+ return new_bits;
+}
+
+} // namespace kudzu
diff --git a/lib/kudzu_buttons_null/public/kudzu_buttons_null/buttons.h b/lib/kudzu_buttons_null/public/kudzu_buttons_null/buttons.h
new file mode 100644
index 0000000..3884c2a
--- /dev/null
+++ b/lib/kudzu_buttons_null/public/kudzu_buttons_null/buttons.h
@@ -0,0 +1,28 @@
+// Copyright 2024 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "kudzu_buttons/buttons.h"
+#include "pw_status/status.h"
+
+namespace kudzu {
+
+class ButtonsNull : public Buttons {
+ public:
+ ButtonsNull();
+ pw::Status Init() override;
+ pw::Result<std::bitset<kButtonCount>> DoUpdate() override;
+};
+
+} // namespace kudzu
diff --git a/lib/kudzu_buttons_pi4ioe5v6416/BUILD.gn b/lib/kudzu_buttons_pi4ioe5v6416/BUILD.gn
new file mode 100644
index 0000000..ec87769
--- /dev/null
+++ b/lib/kudzu_buttons_pi4ioe5v6416/BUILD.gn
@@ -0,0 +1,33 @@
+# Copyright 2024 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+
+config("default_config") {
+ include_dirs = [ "public" ]
+}
+
+pw_source_set("kudzu_buttons_pi4ioe5v6416") {
+ public_configs = [ ":default_config" ]
+ public = [ "public/kudzu_buttons_pi4ioe5v6416/buttons.h" ]
+ deps = [
+ "$dir_pw_log",
+ "$dir_pw_result",
+ "//lib/kudzu_buttons:kudzu_buttons",
+ "//lib/pi4ioe5v6416",
+ ]
+ sources = [ "buttons.cc" ]
+}
diff --git a/lib/kudzu_buttons_pi4ioe5v6416/buttons.cc b/lib/kudzu_buttons_pi4ioe5v6416/buttons.cc
new file mode 100644
index 0000000..76602ac
--- /dev/null
+++ b/lib/kudzu_buttons_pi4ioe5v6416/buttons.cc
@@ -0,0 +1,49 @@
+// Copyright 2024 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "kudzu_buttons/buttons.h"
+
+#include <bitset>
+
+#define PW_LOG_MODULE_NAME "kudzu_buttons_pi4ioe5v6416"
+#define PW_LOG_LEVEL PW_LOG_LEVEL_INFO
+
+#include "kudzu_buttons_pi4ioe5v6416/buttons.h"
+#include "pi4ioe5v6416/device.h"
+#include "pw_log/log.h"
+
+namespace kudzu {
+
+ButtonsPI4IOE5V6416::ButtonsPI4IOE5V6416(pw::pi4ioe5v6416::Device* controller)
+ : controller_(controller) {}
+
+pw::Status ButtonsPI4IOE5V6416::Init() {
+ controller_->Enable();
+ return pw::OkStatus();
+}
+
+pw::Result<std::bitset<kButtonCount>> ButtonsPI4IOE5V6416::DoUpdate() {
+ pw::Result<uint8_t> result = controller_->ReadPort0();
+ if (!result.ok()) {
+ return result.status();
+ }
+ std::bitset<kButtonCount> new_bits = result.value();
+ // The button pins use the GPIO expanders internal pull up resistors so a
+ // press is 0 and a 1 is released. Flip the bits so 1 is a press and 0 is a
+ // release.
+ new_bits.flip();
+ return new_bits;
+}
+
+} // namespace kudzu
diff --git a/lib/kudzu_buttons_pi4ioe5v6416/public/kudzu_buttons_pi4ioe5v6416/buttons.h b/lib/kudzu_buttons_pi4ioe5v6416/public/kudzu_buttons_pi4ioe5v6416/buttons.h
new file mode 100644
index 0000000..4749c50
--- /dev/null
+++ b/lib/kudzu_buttons_pi4ioe5v6416/public/kudzu_buttons_pi4ioe5v6416/buttons.h
@@ -0,0 +1,32 @@
+// Copyright 2024 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "kudzu_buttons/buttons.h"
+#include "pi4ioe5v6416/device.h"
+#include "pw_status/status.h"
+
+namespace kudzu {
+
+class ButtonsPI4IOE5V6416 : public Buttons {
+ public:
+ ButtonsPI4IOE5V6416(pw::pi4ioe5v6416::Device* controller);
+ pw::Status Init() override;
+ pw::Result<std::bitset<kButtonCount>> DoUpdate() override;
+
+ private:
+ pw::pi4ioe5v6416::Device* controller_;
+};
+
+} // namespace kudzu