blob: 519f5f63b17e68873da797a81ebf8ebade79051b [file] [log] [blame]
// Copyright 2022 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 <cstdint>
#include <forward_list>
#define PW_LOG_LEVEL PW_LOG_LEVEL_DEBUG
#include "ansi.h"
#include "pw_board_led/led.h"
#include "pw_color/color.h"
#include "pw_color/colors_endesga32.h"
#include "pw_color/colors_pico8.h"
#include "pw_coordinates/vec2.h"
#include "pw_coordinates/vec_int.h"
#include "pw_display/display.h"
#include "pw_draw/draw.h"
#include "pw_draw/font_set.h"
#include "pw_draw/pigweed_farm.h"
#include "pw_draw/text_area.h"
#include "pw_framebuffer/rgb565.h"
#include "pw_log/log.h"
#include "pw_spin_delay/delay.h"
#include "pw_string/string_builder.h"
#include "pw_sys_io/sys_io.h"
#include "pw_touchscreen/touchscreen.h"
using pw::color::colors_pico8_rgb565;
namespace {
typedef bool (*bool_function_pointer)();
typedef pw::coordinates::Vec3Int (*vec3int_function_pointer)();
pw::framebuffer::FramebufferRgb565 frame_buffer = FramebufferRgb565();
pw::draw::TextArea log_text_area(&frame_buffer, &pw::draw::font6x8);
class DemoDecoder : public AnsiDecoder {
protected:
virtual void SetFgColor(uint8_t r, uint8_t g, uint8_t b) {
log_text_area.SetForegroundColor(pw::color::ColorRGBA(r, g, b).ToRgb565());
}
virtual void SetBgColor(uint8_t r, uint8_t g, uint8_t b) {
log_text_area.SetBackgroundColor(pw::color::ColorRGBA(r, g, b).ToRgb565());
}
virtual void EmitChar(char c) { log_text_area.DrawCharacter(c); }
};
DemoDecoder demo_decoder = DemoDecoder();
void (*write_log_to_screen)(std::string_view) = [](std::string_view log) {
static int cursor_x = 0;
static int cursor_y =
log_text_area.framebuffer->height - log_text_area.current_font->height;
log_text_area.SetCursor(cursor_x, cursor_y);
for (auto c : log) {
demo_decoder.ProcessChar(c);
}
demo_decoder.ProcessChar('\n');
cursor_x = log_text_area.cursor_x;
cursor_y = log_text_area.cursor_y;
pw::sys_io::WriteLine(log).IgnoreError();
};
const wchar_t pigweed_banner[] = {
L"▒█████▄ █▓ ▄███▒ ▒█ ▒█ ░▓████▒ ░▓████▒ ▒▓████▄\n"
L" ▒█░ █░ ░█▒ ██▒ ▀█▒ ▒█░ █ ▒█ ▒█ ▀ ▒█ ▀ ▒█ ▀█▌\n"
L" ▒█▄▄▄█░ ░█▒ █▓░ ▄▄░ ▒█░ █ ▒█ ▒███ ▒███ ░█ █▌\n"
L" ▒█▀ ░█░ ▓█ █▓ ░█░ █ ▒█ ▒█ ▄ ▒█ ▄ ░█ ▄█▌\n"
L" ▒█ ░█░ ░▓███▀ ▒█▓▀▓█░ ░▓████▒ ░▓████▒ ▒▓████▀\n"};
void draw_sprite_and_text_demo() {
int sprite_pos_x = 10;
int sprite_pos_y = 24;
int sprite_scale = 4;
int border_size = 8;
// Draw the dark blue border
pw::draw::DrawRectWH(
&frame_buffer,
sprite_pos_x - border_size,
sprite_pos_y - border_size,
pigweed_farm_sprite_sheet.width * sprite_scale + (border_size * 2),
pigweed_farm_sprite_sheet.height * sprite_scale + (border_size * 2),
colors_pico8_rgb565[COLOR_DARK_BLUE],
true);
// Shrink the border
border_size = 4;
// Draw the light blue background
pw::draw::DrawRectWH(
&frame_buffer,
sprite_pos_x - border_size,
sprite_pos_y - border_size,
pigweed_farm_sprite_sheet.width * sprite_scale + (border_size * 2),
pigweed_farm_sprite_sheet.height * sprite_scale + (border_size * 2),
colors_pico8_rgb565[COLOR_BLUE],
true);
// Draw the Sun
frame_buffer.SetPenColor(colors_pico8_rgb565[COLOR_ORANGE]);
pw::draw::DrawCircle(
&frame_buffer,
sprite_pos_x + (pigweed_farm_sprite_sheet.width * sprite_scale) - 32,
sprite_pos_y,
20,
true);
frame_buffer.SetPenColor(colors_pico8_rgb565[COLOR_YELLOW]);
pw::draw::DrawCircle(
&frame_buffer,
sprite_pos_x + (pigweed_farm_sprite_sheet.width * sprite_scale) - 32,
sprite_pos_y,
18,
true);
// Draw the farm sprite's shadow
pigweed_farm_sprite_sheet.current_index = 1;
pw::draw::DrawSprite(&frame_buffer,
sprite_pos_x + 2,
sprite_pos_y + 2,
&pigweed_farm_sprite_sheet,
4);
// Draw the farm sprite
pigweed_farm_sprite_sheet.current_index = 0;
pw::draw::DrawSprite(
&frame_buffer, sprite_pos_x, sprite_pos_y, &pigweed_farm_sprite_sheet, 4);
// Draw some text
pw::draw::TextArea text_area(&frame_buffer, &pw::draw::font6x8_box_chars);
// Start drawing a x=0, y=72
text_area.DrawCharacter('\n', 0, 72);
// Draw the Pigweed "ASCII" banner.
text_area.SetForegroundColor(colors_pico8_rgb565[COLOR_PINK]);
text_area.SetCharacterWrap(false);
text_area.DrawText(pigweed_banner);
text_area.SetCharacterWrap(true);
text_area.SetFont(&pw::draw::font6x8);
for (int c = pw::draw::font6x8.starting_character;
c <= pw::draw::font6x8.ending_character;
c++) {
if (c % 32 == 0) {
text_area.DrawCharacter('\n');
}
text_area.SetForegroundColor(0);
text_area.SetBackgroundColor(colors_endesga32_rgb565[c % 32]);
text_area.DrawCharacter(c);
}
// Reset background to black
text_area.SetBackgroundColor(0);
text_area.DrawCharacter('\n');
text_area.SetForegroundColor(0xFFFF);
text_area.SetFont(&pw::draw::font6x8);
text_area.DrawTestFontSheet(32, text_area.cursor_x, text_area.cursor_y);
text_area.DrawText("\n\nBox Characters:\n");
text_area.SetFont(&pw::draw::font6x8_box_chars);
text_area.DrawTestFontSheet(32, text_area.cursor_x, text_area.cursor_y);
text_area.SetFont(&pw::draw::font6x8);
text_area.DrawCharacter('\n');
}
bool_function_pointer touch_screen_available_func;
vec3int_function_pointer get_touch_screen_point_func;
bool_function_pointer new_touch_event_func;
bool touch_screen_exists = false;
void setup_touchscreen_functions() {
// Default to using pw::touchscreen but use pw::display if it's supported.
touch_screen_available_func = &pw::touchscreen::Available;
get_touch_screen_point_func = &pw::touchscreen::GetTouchPoint;
new_touch_event_func = &pw::touchscreen::NewTouchEvent;
// Check if pw::display implements touchscreen functions
if (pw::display::TouchscreenAvailable()) {
touch_screen_available_func = &pw::display::TouchscreenAvailable;
get_touch_screen_point_func = &pw::display::GetTouchPoint;
new_touch_event_func = &pw::display::NewTouchEvent;
}
touch_screen_exists = (*touch_screen_available_func)();
}
void create_demo_log_messages() {
// Create some demo log messages.
PW_LOG_CRITICAL("An irrecoverable error has occurred!");
PW_LOG_ERROR("There was an error on our last operation");
PW_LOG_WARN("Looks like something is amiss; consider investigating");
PW_LOG_INFO("The operation went as expected");
PW_LOG_DEBUG("Debug output");
}
// Full resolution
#define FRAMEBUFFER_WIDTH 320
#define FRAMEBUFFER_HEIGHT 240
#define FRAMEBUFFER_UPDATE_FUNCTION Update
// Half resolution
// #define FRAMEBUFFER_WIDTH 160
// #define FRAMEBUFFER_HEIGHT 120
// #define FRAMEBUFFER_UPDATE_FUNCTION UpdatePixelDouble
uint16_t display_framebuffer_data[FRAMEBUFFER_WIDTH * FRAMEBUFFER_HEIGHT];
} // namespace
int main() {
// Timing variables
uint32_t frame_start_millis = pw::spin_delay::Millis();
uint32_t frames = 0;
int frames_per_second = 0;
uint32_t time_start_delta = 0;
uint32_t delta_time = 30000; // Initial guess
uint32_t time_start_button_check = pw::spin_delay::Millis();
uint32_t delta_button_check = pw::spin_delay::Millis();
uint32_t time_start_game_logic = 0;
uint32_t delta_game_logic = 0;
uint32_t time_start_draw_screen = 0;
uint32_t delta_screen_draw = 0;
uint32_t time_start_screen_spi_update = 0;
uint32_t delta_screen_spi_update = 0;
pw::board_led::Init();
frame_buffer.SetFramebufferData(
display_framebuffer_data, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT);
// Clear the framebuffer to black
frame_buffer.SetPenColor(0);
pw::draw::Fill(&frame_buffer);
// Init the display and touchscreen.
PW_LOG_INFO("pw::display::Init()");
pw::display::Init();
PW_LOG_INFO("pw::touchscreen::Init()");
pw::touchscreen::Init();
// Change log output function to write_log_to_screen.
log_text_area.SetBackgroundColor(0);
pw::log_basic::SetOutput(*write_log_to_screen);
// Touchscreen Functions
setup_touchscreen_functions();
// Touch event variables
Vec2 screen_center =
Vec2(frame_buffer.width / 2.0, frame_buffer.height / 2.0);
Vec2 touch_location;
Vec2 touch_location_from_origin;
float touch_location_angle;
float touch_location_length;
pw::coordinates::Vec3Int last_frame_touch_state(0, 0, 0);
draw_sprite_and_text_demo();
// Push the frame buffer to the screen.
pw::display::FRAMEBUFFER_UPDATE_FUNCTION(&frame_buffer);
// Setup the log message button position variables.
pw::draw::TextArea button_text_area(&frame_buffer, &pw::draw::font6x8);
int button_width = 19 * button_text_area.current_font->width;
int button_height = button_text_area.current_font->height;
int button_pos_x = frame_buffer.width - button_width;
int button_pos_y = 0;
button_text_area.SetCursor(button_pos_x, button_pos_y);
button_text_area.SetForegroundColor(colors_pico8_rgb565[COLOR_BLACK]);
button_text_area.SetBackgroundColor(colors_pico8_rgb565[COLOR_BLUE]);
// The display loop.
while (1) {
time_start_delta = pw::spin_delay::Millis();
// Input Update Phase
time_start_button_check = pw::spin_delay::Millis();
pw::coordinates::Vec3Int point = (*get_touch_screen_point_func)();
// Check for touchscreen events.
if (touch_screen_exists && (*new_touch_event_func)()) {
if (point.z > 0) {
bool button_just_pressed = false;
if (point.z != last_frame_touch_state.z)
button_just_pressed = true;
// New touch event
touch_location = Vec2(point.x, point.y);
touch_location_from_origin = touch_location - screen_center;
touch_location_angle = touch_location_from_origin.angle();
touch_location_length = touch_location_from_origin.length();
PW_LOG_DEBUG("Touch: x:%d, y:%d, z:%d", point.x, point.y, point.z);
// Find the angle and length of the touch location from the
// screen_center.
//
// touch_location_angle will fall within the range [-pi, pi]
// Map it to [0, 2*pi] instead.
// if (touch_location_angle < 0)
// touch_location_angle = kTwoPi + touch_location_angle;
// touch_location_angle = kTwoPi - touch_location_angle;
// PW_LOG_DEBUG(" degrees:%f length:%f",
// degrees(touch_location_angle),
// touch_location_length);
// If a button was just pressed, call create_demo_log_messages.
if (button_just_pressed && touch_location.x >= button_pos_x &&
touch_location.y >= button_pos_y &&
touch_location.x < button_pos_x + button_width &&
touch_location.y < button_pos_y + button_height) {
create_demo_log_messages();
}
}
}
last_frame_touch_state.x = point.x;
last_frame_touch_state.y = point.y;
last_frame_touch_state.z = point.z;
// End Input Update Phase
delta_button_check = pw::spin_delay::Millis() - time_start_button_check;
// Game Logic Phase
time_start_game_logic = pw::spin_delay::Millis();
// End Game Logic Phase
delta_game_logic = pw::spin_delay::Millis() - time_start_game_logic;
// Draw Phase
time_start_draw_screen = pw::spin_delay::Millis();
button_text_area.SetCursor(button_pos_x, button_pos_y);
button_text_area.DrawText(" Click to add logs ");
// End Draw Phase
delta_screen_draw = pw::spin_delay::Millis() - time_start_draw_screen;
// Display Write Phase
time_start_screen_spi_update = pw::spin_delay::Millis();
pw::display::FRAMEBUFFER_UPDATE_FUNCTION(&frame_buffer);
// End Display Write Phase
delta_screen_spi_update =
pw::spin_delay::Millis() - time_start_screen_spi_update;
// FPS Count Update
delta_time = pw::spin_delay::Millis() - time_start_delta;
// Every second make a log message.
frames++;
if (pw::spin_delay::Millis() > frame_start_millis + 1000) {
// Log FPS Stats
PW_LOG_INFO(
"Time: %u - FPS: %d", pw::spin_delay::Millis(), frames_per_second);
frames_per_second = frames;
frames = 0;
frame_start_millis = pw::spin_delay::Millis();
}
}
}