blob: bbc5f6b042e78c4c20d6ff788d4596922ddce45a [file] [log] [blame]
// 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