| // Copyright 2023 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 <chrono> |
| #include <cstdint> |
| |
| #define PW_LOG_LEVEL PW_LOG_LEVEL_DEBUG |
| |
| #include "app_common/common.h" |
| #include "graphics/surface.hpp" |
| #include "libkudzu/framecounter.h" |
| #include "libkudzu/random.h" |
| #include "pw_assert/assert.h" |
| #include "pw_assert/check.h" |
| #include "pw_color/color.h" |
| #include "pw_color/colors_endesga32.h" |
| #include "pw_color/colors_pico8.h" |
| #include "pw_display/display.h" |
| #include "pw_framebuffer/framebuffer.h" |
| #include "pw_log/log.h" |
| #include "pw_string/string_builder.h" |
| #include "pw_sys_io/sys_io.h" |
| #include "pw_system/target_hooks.h" |
| #include "pw_thread/detached_thread.h" |
| |
| using pw::color::color_rgb565_t; |
| using pw::color::kColorsPico8Rgb565; |
| using pw::display::Display; |
| using pw::framebuffer::Framebuffer; |
| |
| namespace { |
| |
| struct test_particle { |
| blit::Vec2 pos; |
| blit::Vec2 vel; |
| int age; |
| bool generated = false; |
| }; |
| |
| void rain_generate(test_particle& p, blit::Surface screen) { |
| p.pos = blit::Vec2(GetRandomFloat(screen.bounds.w), -10 + GetRandomFloat(10)); |
| p.vel = blit::Vec2(0, 150); |
| p.age = 0; |
| p.generated = true; |
| }; |
| |
| void rain(blit::Surface screen, |
| pw::chrono::SystemClock::duration elapsed_time, |
| blit::Rect text_position) { |
| static test_particle s[300]; |
| static int generate_index = 0; |
| |
| // Convert to fractional elapsed seconds. |
| auto const elapsed_seconds = |
| std::chrono::duration_cast<std::chrono::duration<float>>(elapsed_time); |
| |
| rain_generate(s[generate_index++], screen); |
| if (generate_index >= 300) |
| generate_index = 0; |
| |
| blit::Vec2 gvec = blit::Vec2(0, 9.8 * 5); |
| blit::Vec2 gravity = gvec * elapsed_seconds.count(); |
| |
| // Add a little padding to the text box. |
| text_position.y -= 2; |
| text_position.h += 2; |
| |
| for (auto& p : s) { |
| if (p.generated) { |
| p.vel += gravity; |
| p.pos += p.vel * elapsed_seconds.count(); |
| |
| int floor = screen.bounds.h - 3; |
| |
| // Rain lands on top of the text. |
| if (text_position.contains(p.pos)) { |
| floor = text_position.y; |
| } |
| |
| if (p.pos.y >= floor) { |
| p.pos.y = floor; |
| float bounce = (GetRandomFloat(10)) / 80.0f; |
| p.vel.y *= -bounce; |
| p.vel.x = (GetRandomFloat(30) - 15); |
| } |
| p.age++; |
| |
| int a = p.age / 2; |
| int r = 100 - (a / 2); |
| int g = 255 - (a * 4); |
| int b = 255 - (a / 2); |
| |
| if (p.vel.length() > 20) { |
| screen.pen = blit::Pen(b, g, r, 100); |
| screen.pixel(p.pos + blit::Point(0, -1)); |
| screen.pen = blit::Pen(b, g, r, 160); |
| screen.pixel(p.pos + blit::Point(0, 1)); |
| } |
| screen.pen = blit::Pen(b, g, r, 180); |
| screen.pixel(p.pos); |
| } |
| } |
| }; |
| |
| void MainTask(void*) { |
| // Timing variables |
| kudzu::FrameCounter frame_counter = kudzu::FrameCounter(); |
| |
| PW_CHECK_OK(Common::Init()); |
| |
| Display& display = Common::GetDisplay(); |
| Framebuffer framebuffer = display.GetFramebuffer(); |
| PW_ASSERT(framebuffer.is_valid()); |
| |
| kudzu::Buttons& kudzu_buttons = Common::GetButtons(); |
| |
| blit::Surface screen = blit::Surface( |
| (uint8_t*)framebuffer.data(), |
| blit::PixelFormat::RGB565, |
| blit::Size(framebuffer.size().width, framebuffer.size().height)); |
| screen.pen = blit::Pen(0, 0, 0, 255); |
| screen.clear(); |
| |
| display.ReleaseFramebuffer(std::move(framebuffer)); |
| const int32_t init_text_pos_x = screen.bounds.w / 2; |
| int32_t text_pos_x = init_text_pos_x; |
| const int32_t init_text_pos_y = screen.bounds.h * 0.75; |
| int32_t text_pos_y = init_text_pos_y; |
| |
| // NOTE: colors are in BGR order |
| static constexpr const std::array text_colors{ |
| blit::Pen(0xFF, 0xFF, 0xFF), |
| blit::Pen(0xB8, 0xA9, 0xF5), |
| blit::Pen(0xFA, 0xCE, 0x5B), |
| }; |
| int text_color_index = 0; |
| |
| static const std::array fonts{ |
| blit::minimal_font, |
| blit::outline_font, |
| blit::fat_font, |
| }; |
| int font_index = 0; |
| |
| // The display loop. |
| while (1) { |
| kudzu_buttons.Update(); |
| if (kudzu_buttons.Held(kudzu::button::up)) { |
| text_pos_y -= 1; |
| } |
| if (kudzu_buttons.Held(kudzu::button::down)) { |
| text_pos_y += 1; |
| } |
| if (kudzu_buttons.Held(kudzu::button::left)) { |
| text_pos_x -= 1; |
| } |
| if (kudzu_buttons.Held(kudzu::button::right)) { |
| text_pos_x += 1; |
| } |
| if (kudzu_buttons.Pressed(kudzu::button::a)) { |
| text_color_index = (text_color_index + 1) % text_colors.size(); |
| } |
| if (kudzu_buttons.Pressed(kudzu::button::b)) { |
| font_index = (font_index + 1) % fonts.size(); |
| } |
| if (kudzu_buttons.Pressed(kudzu::button::start)) { |
| text_pos_x = init_text_pos_x; |
| text_pos_y = init_text_pos_y; |
| } |
| |
| frame_counter.StartFrame(); |
| |
| framebuffer = display.GetFramebuffer(); |
| PW_ASSERT(framebuffer.is_valid()); |
| screen.data = (uint8_t*)framebuffer.data(); |
| |
| // Draw Phase |
| // Clear the screen |
| screen.pen = blit::Pen(0, 0, 0); |
| screen.clear(); |
| |
| // Draw 32blit animation |
| std::string text = "Pigweed + 32blit"; |
| auto text_size = screen.measure_text(text, fonts[font_index], true); |
| |
| // Ensure that the text stays on-screen by wrapping its position around. |
| int32_t width = screen.bounds.w; |
| int32_t height = screen.bounds.h; |
| |
| int32_t min_x = 0 - (text_size.w / 2); |
| int32_t max_x = width + (text_size.w / 2); |
| int32_t min_y = 0 - (text_size.h / 2); |
| int32_t max_y = height + (text_size.h / 2); |
| |
| if (text_pos_x < min_x) { |
| text_pos_x = max_x; |
| } |
| if (text_pos_x > max_x) { |
| text_pos_x = min_x; |
| } |
| if (text_pos_y < min_y) { |
| text_pos_y = max_y; |
| } |
| if (text_pos_y > max_y) { |
| text_pos_y = min_y; |
| } |
| |
| blit::Rect text_rect(blit::Point(text_pos_x - (text_size.w / 2), |
| text_pos_y - (text_size.h / 2)), |
| text_size); |
| |
| rain(screen, frame_counter.LastFrameDuration(), text_rect); |
| screen.pen = text_colors[text_color_index]; |
| screen.text( |
| text, fonts[font_index], text_rect, true, blit::TextAlign::top_left); |
| |
| // Update timers |
| frame_counter.EndDraw(); |
| |
| display.ReleaseFramebuffer(std::move(framebuffer)); |
| frame_counter.EndFlush(); |
| |
| // Every second make a log message. |
| frame_counter.LogTiming(); |
| } |
| } |
| |
| } // namespace |
| |
| namespace pw::system { |
| |
| void UserAppInit() { |
| PW_LOG_INFO("UserAppInit"); |
| pw::thread::DetachedThread(Common::DisplayDrawThreadOptions(), MainTask); |
| } |
| |
| } // namespace pw::system |