Add mana menu
This commit is contained in:
parent
85dcf5f32e
commit
b28f81404e
@ -13,6 +13,8 @@ add_executable(mtgcard
|
||||
src/lib.cpp
|
||||
src/flash.cpp
|
||||
src/cardslot.cpp
|
||||
src/icons.cpp
|
||||
src/manamenu.cpp
|
||||
|
||||
src/usb_descriptors.c
|
||||
|
||||
|
||||
@ -13,8 +13,11 @@ private:
|
||||
public:
|
||||
CardSlot(lib::Flash& flash, uint16_t card_index);
|
||||
|
||||
void mark_unused(lib::Flash& flash);
|
||||
void set_name(const uint8_t* name, uint8_t length);
|
||||
void set_cmc(uint8_t cmc);
|
||||
void set_colors(uint8_t colors);
|
||||
|
||||
void mark_unused(lib::Flash& flash);
|
||||
void save_data(lib::Flash& flash);
|
||||
void erase(lib::Flash& flash);
|
||||
|
||||
@ -42,8 +45,10 @@ private:
|
||||
struct
|
||||
{
|
||||
bool in_use : 1;
|
||||
uint32_t times_erased : 26;
|
||||
uint8_t cmc : 5;
|
||||
uint8_t colors : 5;
|
||||
uint8_t name_length : NAME_BITS;
|
||||
uint32_t times_erased;
|
||||
} status;
|
||||
uint8_t name[MAX_NAME_LENGTH];
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#ifndef ICONS_H
|
||||
#define ICONS_H
|
||||
|
||||
#include <pico/stdlib.h>
|
||||
|
||||
extern unsigned char __0_svg_raw[];
|
||||
extern int __0_svg_raw_len;
|
||||
|
||||
@ -82,4 +84,20 @@ extern int G_svg_raw_len;
|
||||
extern unsigned char C_svg_raw[];
|
||||
extern int C_svg_raw_len;
|
||||
|
||||
namespace icon
|
||||
{
|
||||
|
||||
constexpr const unsigned char* NUMBER_ICONS[21]
|
||||
= { __0_svg_raw, __1_svg_raw, __2_svg_raw, __3_svg_raw, __4_svg_raw,
|
||||
__5_svg_raw, __6_svg_raw, __7_svg_raw, __8_svg_raw, __9_svg_raw,
|
||||
__10_svg_raw, __11_svg_raw, __12_svg_raw, __13_svg_raw, __14_svg_raw,
|
||||
__15_svg_raw, __16_svg_raw, __17_svg_raw, __18_svg_raw, __19_svg_raw,
|
||||
__20_svg_raw };
|
||||
|
||||
constexpr int LENGTH = 32 * 32 * 3;
|
||||
|
||||
const unsigned char* wubrgc(uint8_t value);
|
||||
|
||||
}
|
||||
|
||||
#endif // ICONS_H
|
||||
|
||||
61
include/menu.h
Normal file
61
include/menu.h
Normal file
@ -0,0 +1,61 @@
|
||||
#ifndef MENU_H
|
||||
#define MENU_H
|
||||
|
||||
#include "display.h"
|
||||
|
||||
#include <pico/stdlib.h>
|
||||
|
||||
class Menu
|
||||
{
|
||||
public:
|
||||
static constexpr int HOLD_MS_THRESHOLD = 250;
|
||||
|
||||
explicit Menu(lib::Display& display);
|
||||
virtual ~Menu() = default;
|
||||
|
||||
virtual void onTick(float dt) = 0;
|
||||
|
||||
virtual void onLeftPressed() = 0;
|
||||
virtual void onLeftHeld() = 0;
|
||||
|
||||
virtual void onMenuPressed() = 0;
|
||||
virtual void onMenuHeld() = 0;
|
||||
|
||||
virtual void onRightPressed() = 0;
|
||||
virtual void onRightHeld() = 0;
|
||||
|
||||
protected:
|
||||
lib::Display* const display;
|
||||
};
|
||||
|
||||
namespace menus
|
||||
{
|
||||
|
||||
class ManaMenu : public Menu
|
||||
{
|
||||
public:
|
||||
explicit ManaMenu(lib::Display& display);
|
||||
~ManaMenu() override = default;
|
||||
|
||||
void onTick(float dt) override;
|
||||
|
||||
void onLeftPressed() override;
|
||||
void onLeftHeld() override;
|
||||
|
||||
void onMenuPressed() override;
|
||||
void onMenuHeld() override;
|
||||
|
||||
void onRightPressed() override;
|
||||
void onRightHeld() override;
|
||||
|
||||
private:
|
||||
uint8_t current;
|
||||
bool going_left;
|
||||
float progress;
|
||||
};
|
||||
|
||||
constexpr size_t MAX_MENU_SIZE = sizeof(ManaMenu);
|
||||
|
||||
}
|
||||
|
||||
#endif // MENU_H
|
||||
@ -13,13 +13,6 @@ uint16_t CardSlot::get_image_start_page_index() const
|
||||
return card_index * TOTAL_PAGE_COUNT + 1;
|
||||
}
|
||||
|
||||
void CardSlot::mark_unused(lib::Flash& flash)
|
||||
{
|
||||
flash.read_page(card_index * TOTAL_PAGE_COUNT);
|
||||
flash.page()[0] &= 0xFE;
|
||||
flash.write_page(card_index * TOTAL_PAGE_COUNT);
|
||||
}
|
||||
|
||||
void CardSlot::set_name(const uint8_t* name, uint8_t name_length)
|
||||
{
|
||||
if (name_length > 32)
|
||||
@ -31,13 +24,28 @@ void CardSlot::set_name(const uint8_t* name, uint8_t name_length)
|
||||
status.name_length = name_length;
|
||||
}
|
||||
|
||||
void CardSlot::set_cmc(uint8_t cmc) { status.cmc = cmc > 20 ? 20 : cmc; }
|
||||
|
||||
void CardSlot::set_colors(uint8_t colors) { status.colors = colors & 0b11111; }
|
||||
|
||||
void CardSlot::mark_unused(lib::Flash& flash)
|
||||
{
|
||||
flash.read_page(card_index * TOTAL_PAGE_COUNT);
|
||||
flash.page()[0] &= 0xFE;
|
||||
flash.write_page(card_index * TOTAL_PAGE_COUNT);
|
||||
}
|
||||
|
||||
void CardSlot::save_data(lib::Flash& flash)
|
||||
{
|
||||
// if we're saving data, the slot is in use
|
||||
status.in_use = true;
|
||||
|
||||
// and the erase count should be updated
|
||||
status.times_erased += 1;
|
||||
|
||||
uint32_t raw_status = (status.in_use << 0) | (status.name_length << 1)
|
||||
| (status.times_erased << (NAME_BITS + 1));
|
||||
| (status.cmc << (NAME_BITS + 1))
|
||||
| (status.colors << (NAME_BITS + 6));
|
||||
auto& page = flash.page();
|
||||
|
||||
page[0] = (raw_status >> 0) & 0xFF;
|
||||
@ -45,7 +53,12 @@ void CardSlot::save_data(lib::Flash& flash)
|
||||
page[2] = (raw_status >> 16) & 0xFF;
|
||||
page[3] = (raw_status >> 24) & 0xFF;
|
||||
|
||||
memcpy(page + 4, name, status.name_length);
|
||||
page[4] = (status.times_erased >> 0) & 0xFF;
|
||||
page[5] = (status.times_erased >> 8) & 0xFF;
|
||||
page[6] = (status.times_erased >> 16) & 0xFF;
|
||||
page[7] = (status.times_erased >> 24) & 0xFF;
|
||||
|
||||
memcpy(page + 8, name, status.name_length);
|
||||
|
||||
flash.write_page(card_index * TOTAL_PAGE_COUNT);
|
||||
}
|
||||
@ -101,20 +114,24 @@ void CardSlot::load_data(lib::Flash& flash)
|
||||
|
||||
status.in_use = raw_status & 0x01;
|
||||
status.name_length = raw_status & 0x3E;
|
||||
status.times_erased = raw_status & 0xFFFFFFC0;
|
||||
status.cmc = raw_status & 0x7C0;
|
||||
status.colors = raw_status & 0xF800;
|
||||
|
||||
memcpy(name, page + 4, status.name_length);
|
||||
status.times_erased
|
||||
= page[4] | (page[5] << 8) | (page[6] << 16) | (page[7] << 24);
|
||||
|
||||
memcpy(name, page + 8, status.name_length);
|
||||
}
|
||||
|
||||
int32_t CardSlot::get_unused(lib::Flash& flash)
|
||||
{
|
||||
int min_unused = INT32_MAX;
|
||||
uint32_t min_unused = UINT32_MAX;
|
||||
int min_unused_index = -1;
|
||||
|
||||
for (int i = 0; i < MAX_CARDS; i++)
|
||||
{
|
||||
CardSlot c(flash, i);
|
||||
if (!c.is_used() && c.times_erased() < min_unused)
|
||||
if (!c.is_used() && c.times_erased() <= min_unused)
|
||||
{
|
||||
min_unused = c.times_erased();
|
||||
min_unused_index = i;
|
||||
|
||||
29
src/icons.cpp
Normal file
29
src/icons.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
#include "icons.h"
|
||||
|
||||
const unsigned char* icon::wubrgc(uint8_t value)
|
||||
{
|
||||
if (value & 0b10000)
|
||||
{
|
||||
return W_svg_raw;
|
||||
}
|
||||
else if (value & 0b1000)
|
||||
{
|
||||
return U_svg_raw;
|
||||
}
|
||||
else if (value & 0b100)
|
||||
{
|
||||
return B_svg_raw;
|
||||
}
|
||||
else if (value & 0b10)
|
||||
{
|
||||
return R_svg_raw;
|
||||
}
|
||||
else if (value & 0b1)
|
||||
{
|
||||
return G_svg_raw;
|
||||
}
|
||||
else
|
||||
{
|
||||
return C_svg_raw;
|
||||
}
|
||||
}
|
||||
105
src/main.cpp
105
src/main.cpp
@ -1,9 +1,10 @@
|
||||
#include "cardslot.h"
|
||||
#include "display.h"
|
||||
#include "flash.h"
|
||||
#include "icons.h"
|
||||
#include "menu.h"
|
||||
#include "pixelstream.h"
|
||||
|
||||
#include <new>
|
||||
#include <pico/binary_info.h>
|
||||
#include <pico/stdlib.h>
|
||||
#include <tusb.h>
|
||||
@ -36,6 +37,9 @@ Flash flash(FLASH_SCK, FLASH_TX, FLASH_RX, FLASH_CS, 70000000);
|
||||
enum class RX
|
||||
{
|
||||
WAITING,
|
||||
GETTING_CMC,
|
||||
GETTING_COLORS,
|
||||
GETTING_NAME_LENGTH,
|
||||
GETTING_NAME,
|
||||
GETTING_IMAGE
|
||||
};
|
||||
@ -50,6 +54,9 @@ struct
|
||||
uint8_t written;
|
||||
} name;
|
||||
|
||||
uint8_t cmc;
|
||||
uint8_t colors;
|
||||
|
||||
struct
|
||||
{
|
||||
size_t written = 0;
|
||||
@ -61,6 +68,27 @@ struct
|
||||
int32_t card_index = 0;
|
||||
} rx;
|
||||
|
||||
uint8_t
|
||||
MENU_MEMORY[menus::MAX_MENU_SIZE * 5]; // menu stack is at most 5 menus deep
|
||||
int8_t current_menu = -1;
|
||||
|
||||
Menu* get_current_menu()
|
||||
{
|
||||
return reinterpret_cast<Menu*>(MENU_MEMORY
|
||||
+ current_menu * menus::MAX_MENU_SIZE);
|
||||
}
|
||||
|
||||
template <typename T> void push_menu()
|
||||
{
|
||||
current_menu += 1;
|
||||
new (get_current_menu()) T(display);
|
||||
}
|
||||
void pop_menu()
|
||||
{
|
||||
get_current_menu()->~Menu();
|
||||
current_menu -= 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
@ -83,56 +111,43 @@ int main()
|
||||
|
||||
uint16_t card_index = 0;
|
||||
absolute_time_t last_press = get_absolute_time();
|
||||
absolute_time_t previous_time = last_press;
|
||||
|
||||
const auto& pixel_fn = [](void* pixels, uint8_t data)
|
||||
{ static_cast<PixelStream*>(pixels)->write(data); };
|
||||
|
||||
{
|
||||
auto pixels = display.pixels();
|
||||
CardSlot(flash, 0).each_pixel(flash, pixel_fn, &pixels);
|
||||
}
|
||||
push_menu<menus::ManaMenu>();
|
||||
|
||||
while (true)
|
||||
{
|
||||
absolute_time_t current = get_absolute_time();
|
||||
float dt = absolute_time_diff_us(previous_time, current) / 1000000.0f;
|
||||
|
||||
if (!gpio_get(BUTTON_LEFT)
|
||||
&& absolute_time_diff_us(last_press, current) > 1000 * 10)
|
||||
&& absolute_time_diff_us(last_press, current) > 1000 * 150)
|
||||
{
|
||||
do
|
||||
{
|
||||
card_index -= 1;
|
||||
if (card_index >= CardSlot::MAX_CARDS)
|
||||
{
|
||||
card_index = CardSlot::MAX_CARDS - 1;
|
||||
}
|
||||
} while (!CardSlot(flash, card_index).is_used());
|
||||
get_current_menu()->onLeftPressed();
|
||||
|
||||
auto pixels = display.pixels();
|
||||
CardSlot(flash, card_index).each_pixel(flash, pixel_fn, &pixels);
|
||||
last_press = current;
|
||||
}
|
||||
|
||||
if (!gpio_get(BUTTON_MIDDLE)
|
||||
&& absolute_time_diff_us(last_press, current) > 1000 * 150)
|
||||
{
|
||||
get_current_menu()->onMenuPressed();
|
||||
|
||||
last_press = current;
|
||||
}
|
||||
|
||||
if (!gpio_get(BUTTON_RIGHT)
|
||||
&& absolute_time_diff_us(last_press, current) > 1000 * 10)
|
||||
&& absolute_time_diff_us(last_press, current) > 1000 * 150)
|
||||
{
|
||||
do
|
||||
{
|
||||
card_index += 1;
|
||||
if (card_index >= CardSlot::MAX_CARDS)
|
||||
{
|
||||
card_index = 0;
|
||||
}
|
||||
} while (!CardSlot(flash, card_index).is_used());
|
||||
|
||||
auto pixels = display.pixels();
|
||||
CardSlot(flash, card_index).each_pixel(flash, pixel_fn, &pixels);
|
||||
get_current_menu()->onRightPressed();
|
||||
|
||||
last_press = current;
|
||||
}
|
||||
|
||||
tud_task();
|
||||
get_current_menu()->onTick(dt);
|
||||
|
||||
previous_time = current;
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,11 +169,31 @@ void tud_vendor_rx_cb(uint8_t itf, const uint8_t* buffer, uint16_t bufsize)
|
||||
return;
|
||||
}
|
||||
|
||||
rx.state = RX::GETTING_NAME;
|
||||
rx.state = RX::GETTING_CMC;
|
||||
|
||||
rx.card_index = CardSlot::get_unused(flash);
|
||||
}
|
||||
else if (rx.state == RX::GETTING_CMC)
|
||||
{
|
||||
rx.state = RX::GETTING_COLORS;
|
||||
|
||||
rx.name.size = buffer[0] > 32 ? 32 : buffer[0];
|
||||
rx.cmc = buffer[read] > 20 ? 20 : buffer[read];
|
||||
|
||||
read += 1;
|
||||
}
|
||||
else if (rx.state == RX::GETTING_COLORS)
|
||||
{
|
||||
rx.state = RX::GETTING_NAME_LENGTH;
|
||||
|
||||
rx.colors = buffer[read] & 0b11111;
|
||||
|
||||
read += 1;
|
||||
}
|
||||
else if (rx.state == RX::GETTING_NAME_LENGTH)
|
||||
{
|
||||
rx.state = RX::GETTING_NAME;
|
||||
|
||||
rx.name.size = buffer[read] > 32 ? 32 : buffer[read];
|
||||
rx.name.written = 0;
|
||||
|
||||
read += 1;
|
||||
@ -186,11 +221,13 @@ void tud_vendor_rx_cb(uint8_t itf, const uint8_t* buffer, uint16_t bufsize)
|
||||
CardSlot slot(flash, rx.card_index);
|
||||
slot.erase(flash);
|
||||
slot.set_name(rx.name.buffer, rx.name.size);
|
||||
slot.set_cmc(rx.cmc);
|
||||
slot.set_colors(rx.colors);
|
||||
slot.save_data(flash);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (rx.state == RX::GETTING_IMAGE)
|
||||
{
|
||||
size_t copy_size = bufsize - read < Flash::Page::SIZE - rx.image.written
|
||||
? bufsize - read
|
||||
|
||||
151
src/manamenu.cpp
Normal file
151
src/manamenu.cpp
Normal file
@ -0,0 +1,151 @@
|
||||
#include "icons.h"
|
||||
#include "menu.h"
|
||||
#include "pixelstream.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr uint8_t COLORS[] = { 0, 0b1, 0b10, 0b100, 0b1000, 0b10000 };
|
||||
|
||||
}
|
||||
|
||||
using namespace menus;
|
||||
|
||||
Menu::Menu(lib::Display& display) :
|
||||
display(&display)
|
||||
{
|
||||
}
|
||||
|
||||
ManaMenu::ManaMenu(lib::Display& display) :
|
||||
Menu(display),
|
||||
current(0),
|
||||
going_left(false),
|
||||
progress(0.999f)
|
||||
{
|
||||
}
|
||||
|
||||
void ManaMenu::onTick(float dt)
|
||||
{
|
||||
constexpr float PI = 3.1415926f;
|
||||
constexpr float STEP = 2 * PI / 6.0f;
|
||||
constexpr float RADIUS = 80;
|
||||
|
||||
if (progress < 1.0f)
|
||||
{
|
||||
const float offset = (going_left ? -1 : 1) * STEP * progress + STEP / 2.0f;
|
||||
|
||||
// erase previous
|
||||
for (int i = 0; i < sizeof(COLORS) / sizeof(*COLORS); i++)
|
||||
{
|
||||
const float angle = (i - current) * STEP - offset;
|
||||
|
||||
const float x = cosf(angle) * RADIUS;
|
||||
const float y = sinf(angle) * RADIUS;
|
||||
|
||||
const int left = x - 20 + 120;
|
||||
const int top = y - 20 + 160;
|
||||
|
||||
// top rectangle
|
||||
{
|
||||
display->set_update_area(left, top, 38, 5);
|
||||
auto pixels = display->pixels();
|
||||
for (int pixel = 0; pixel < 38 * 5; pixel++)
|
||||
{
|
||||
pixels.write(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// bottom rectangle
|
||||
{
|
||||
display->set_update_area(left, top + 36, 38, 5);
|
||||
auto pixels = display->pixels();
|
||||
for (int pixel = 0; pixel < 38 * 5; pixel++)
|
||||
{
|
||||
pixels.write(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// left rectangle
|
||||
{
|
||||
display->set_update_area(left, top + 3, 5, 38);
|
||||
auto pixels = display->pixels();
|
||||
for (int pixel = 0; pixel < 5 * 38; pixel++)
|
||||
{
|
||||
pixels.write(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// right rectangle
|
||||
{
|
||||
display->set_update_area(left + 36, top + 3, 5, 38);
|
||||
auto pixels = display->pixels();
|
||||
for (int pixel = 0; pixel < 5 * 38; pixel++)
|
||||
{
|
||||
pixels.write(0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
progress += dt * 1.2f;
|
||||
if (progress > 1.0f)
|
||||
{
|
||||
progress = 1.0f;
|
||||
}
|
||||
|
||||
// write new
|
||||
for (int i = 0; i < sizeof(COLORS) / sizeof(*COLORS); i++)
|
||||
{
|
||||
const float angle = (i - current) * STEP - offset;
|
||||
|
||||
const float x = cosf(angle) * RADIUS;
|
||||
const float y = sinf(angle) * RADIUS;
|
||||
|
||||
const int left = x - 15 + 120;
|
||||
const int top = y - 15 + 160;
|
||||
|
||||
display->set_update_area(left, top, 31, 32);
|
||||
auto pixels = display->pixels();
|
||||
for (int pixel = 0; pixel < icon::LENGTH; pixel++)
|
||||
{
|
||||
pixels.write(icon::wubrgc(COLORS[i])[pixel]);
|
||||
}
|
||||
}
|
||||
|
||||
sleep_ms(1);
|
||||
}
|
||||
}
|
||||
|
||||
void ManaMenu::onLeftPressed()
|
||||
{
|
||||
if (current == 0)
|
||||
{
|
||||
current = 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
current -= 1;
|
||||
}
|
||||
|
||||
progress = 0.0f;
|
||||
}
|
||||
void ManaMenu::onLeftHeld() {}
|
||||
|
||||
void ManaMenu::onMenuPressed() {}
|
||||
void ManaMenu::onMenuHeld() {}
|
||||
|
||||
void ManaMenu::onRightPressed()
|
||||
{
|
||||
if (current == 5)
|
||||
{
|
||||
current = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
current += 1;
|
||||
}
|
||||
|
||||
progress = 0.0f;
|
||||
}
|
||||
void ManaMenu::onRightHeld() {}
|
||||
Loading…
x
Reference in New Issue
Block a user