238 lines
9.1 KiB
C++
238 lines
9.1 KiB
C++
#include <array>
|
|
#include <bits/locale_classes.h>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <ctime>
|
|
|
|
#include "Font5x7FixedMono.h"
|
|
#include "alarm_sound.h"
|
|
#include "control.h"
|
|
#include "display.h"
|
|
#include "gc9a01.h"
|
|
#include "macros.h"
|
|
#include "lwip/arch.h"
|
|
#include "multicore_events.h"
|
|
#include "pico/multicore.h"
|
|
#include "pico/time.h"
|
|
#include "sound.h"
|
|
#include "timezones.h"
|
|
|
|
|
|
bool secondary_button_override = false;
|
|
|
|
struct configuration {
|
|
char* text;
|
|
void (*set_text)(const configuration* self);
|
|
void (*update)(configuration* self,bool hard);
|
|
uint32_t local_conf; // can be used as diferent data types
|
|
uint8_t additional_data;
|
|
bool override_secondary_button;
|
|
};
|
|
|
|
void timezone_set_text(const configuration* self) {sprintf(self->text, "TIMEZONE: %s", timezones[self->local_conf].zone_name);}
|
|
void timezone_update(configuration* self,bool hard) {
|
|
uint16_t selected_option = get_knob_percentage();
|
|
self->additional_data %= std::size(timezones)/100;
|
|
selected_option+= self->additional_data*100;
|
|
self->local_conf = selected_option >= std::size(timezones) ? std::size(timezones)-1 : selected_option;
|
|
if (hard) {
|
|
timezone_index = self->local_conf;
|
|
// todo store to flash
|
|
}
|
|
}
|
|
|
|
void volume_set_text(const configuration* self) {sprintf(self->text, "VOLUME: %0.1f", *(float*)&self->local_conf);}
|
|
void volume_update(configuration* self,bool hard)
|
|
{
|
|
*(float*)&self->local_conf = static_cast<float>(get_knob_percentage())/10.0f;
|
|
if (hard) {
|
|
volume_multiplier = *(float*)&self->local_conf;
|
|
// todo store to flash
|
|
}
|
|
}
|
|
|
|
void config_page() {
|
|
char volume_str[17];
|
|
char timezone_str[41];
|
|
std::array<configuration, 2> options = {{{timezone_str,&timezone_set_text,&timezone_update,timezone_index, 0,true}, {volume_str,&volume_set_text,&volume_update,*(uint32_t*)&volume_multiplier, 0, false}}};
|
|
const GFXfont *font = &Font5x7FixedMono;
|
|
|
|
draw_ui_circle(RED);
|
|
multicore_fifo_drain();
|
|
uint8_t selected_option;
|
|
bool configuring = false;
|
|
while (true) {
|
|
for (uint8_t i = 0; i < ARRAY_LENGTH(options); i++) {
|
|
options[i].set_text(&options[i]);
|
|
}
|
|
|
|
if (!configuring)
|
|
{
|
|
selected_option = std::min(
|
|
static_cast<uint8_t>((get_knob_percentage() / 100.0f) * options.size()),
|
|
static_cast<uint8_t>(options.size() - 1));
|
|
}
|
|
|
|
|
|
// Find the actual maximum text width that will be rendered
|
|
uint16_t max_text_width_pixels = 0;
|
|
for (uint8_t i = 0; i < options.size(); i++) {
|
|
uint16_t text_width = strlen(options[i].text) * 5;
|
|
if (text_width > max_text_width_pixels) {
|
|
max_text_width_pixels = text_width;
|
|
}
|
|
}
|
|
|
|
// Add proper padding to ensure text doesn't touch cyan lines
|
|
const uint8_t horizontal_padding = 15; // Increased padding
|
|
const uint8_t half_boundary_width = (max_text_width_pixels + (2 * horizontal_padding)) / 2;
|
|
constexpr uint8_t center_pos = (MAX_X / 2);
|
|
|
|
// Calculate cyan line positions with proper spacing
|
|
uint16_t left_cyan_x = center_pos - half_boundary_width;
|
|
uint16_t right_cyan_x = center_pos + half_boundary_width;
|
|
|
|
// Get vertical boundaries
|
|
std::array<uint16_t, 2> circle_y_pos = get_ui_circle_vertical_pos(right_cyan_x);
|
|
uint16_t y_offset = circle_y_pos[0] + 2;
|
|
|
|
// right
|
|
gc9a01_vline(&display, right_cyan_x, y_offset,
|
|
(circle_y_pos[1] - 1) - y_offset, CYAN);
|
|
// left
|
|
gc9a01_vline(&display, left_cyan_x, y_offset,
|
|
(circle_y_pos[1] - 1) - y_offset, CYAN);
|
|
|
|
const uint8_t half_y_font = static_cast<uint8_t>(
|
|
roundf(static_cast<float>(7) / static_cast<float>(2)));
|
|
y_offset += 20;
|
|
|
|
// Render text with proper horizontal centering
|
|
for (uint8_t i = 0; i < options.size(); i++) {
|
|
uint16_t text_width_pixels = strlen(options[i].text) * 5;
|
|
|
|
// Center each text line horizontally within the cyan boundaries
|
|
uint16_t x_pos = (center_pos - (text_width_pixels / 2));
|
|
|
|
// Ensure text stays within cyan line boundaries
|
|
if (x_pos < left_cyan_x + horizontal_padding) {
|
|
x_pos = left_cyan_x + horizontal_padding;
|
|
} else if (x_pos + text_width_pixels > right_cyan_x - horizontal_padding) {
|
|
x_pos = right_cyan_x - horizontal_padding - text_width_pixels;
|
|
}
|
|
|
|
gc9a01_text_gfx_buffered(&display, font, options[i].text, x_pos-10 /*MAGIC IDK WHY*/, y_offset, i == selected_option ? configuring ? RED : GREEN : WHITE, BLACK);
|
|
y_offset += half_y_font;
|
|
|
|
// Draw separator line within cyan boundaries
|
|
gc9a01_hline(&display, left_cyan_x + 2, y_offset,
|
|
(right_cyan_x - left_cyan_x) - 4, WHITE);
|
|
y_offset += half_y_font * 2;
|
|
}
|
|
|
|
switch (static_cast<multicore_event_t>(multicore_fifo_pop_blocking())) {
|
|
case PRIMARY_BUTTON_PRESSED:
|
|
if (configuring) {
|
|
options[selected_option].update(&options[selected_option], true);
|
|
secondary_button_override = false;
|
|
} else {
|
|
if (options[selected_option].override_secondary_button) {
|
|
secondary_button_override = true;
|
|
}
|
|
gc9a01_fill_rect(&display,left_cyan_x,circle_y_pos[0]+1,right_cyan_x-left_cyan_x,(circle_y_pos[1]-circle_y_pos[0])-2,BLACK);
|
|
options[selected_option].update(&options[selected_option], false);
|
|
}
|
|
configuring = !configuring;
|
|
break;
|
|
case SECONDARY_BUTTON_PRESSED:
|
|
options[selected_option].additional_data++;
|
|
gc9a01_fill_rect(&display,left_cyan_x,circle_y_pos[0]+1,right_cyan_x-left_cyan_x,(circle_y_pos[1]-circle_y_pos[0])-2,BLACK);
|
|
options[selected_option].update(&options[selected_option], false);
|
|
break;
|
|
case KNOB_CHANGE:
|
|
if (configuring) {
|
|
gc9a01_fill_rect(&display,left_cyan_x,circle_y_pos[0]+1,right_cyan_x-left_cyan_x,(circle_y_pos[1]-circle_y_pos[0])-2,BLACK);
|
|
options[selected_option].update(&options[selected_option], false);
|
|
}
|
|
break;
|
|
}
|
|
multicore_fifo_drain(); // prevent event stucking
|
|
}
|
|
}
|
|
|
|
|
|
void time_page() {
|
|
draw_ui_circle(MAGENTA);
|
|
print_time(true);
|
|
while (true) {
|
|
multicore_fifo_drain();
|
|
sleep_ms(500);
|
|
print_time();
|
|
}
|
|
}
|
|
|
|
static uint16_t get_alarm_x(uint8_t index) {
|
|
return (index % 2 == 0) ? 40 : (MAX_X - 40 - ALARM_BOX_WIDTH);
|
|
}
|
|
|
|
static uint8_t get_alarm_y(uint8_t index) {
|
|
return 40 + (index / 2) * (ALARM_BOX_HEIGHT + 5);
|
|
}
|
|
|
|
|
|
[[noreturn]] void alarm_set_page() {
|
|
draw_ui_circle(BLUE);
|
|
full_redraw:
|
|
multicore_fifo_drain();
|
|
uint8_t y_pos = 40;
|
|
|
|
uint8_t selected_index = static_cast<uint8_t>(static_cast<float>(get_knob_percentage())/100.0f*static_cast<float>(alarms.size()));
|
|
for (uint8_t i = 0; i < alarms.size(); i++) {
|
|
print_alarm(&alarms[i], i == selected_index ? SELECTED : NOT_SELECTED, !(i % 2) ? 40 : MAX_X-40-ALARM_BOX_WIDTH,y_pos);
|
|
if (i % 2) {
|
|
y_pos += ALARM_BOX_HEIGHT+5;
|
|
}
|
|
}
|
|
print_add_button(MAX_X/2,MAX_Y-14,20,selected_index == alarms.size() ? SELECTED : NOT_SELECTED);
|
|
|
|
while (true) {
|
|
switch (static_cast<multicore_event_t>(multicore_fifo_pop_blocking())) {
|
|
case PRIMARY_BUTTON_PRESSED:
|
|
if (selected_index == alarms.size()) {
|
|
alarms.emplace_back(0, 0, std::array<bool, 7>{false, false, false, false, false, false, false}, false, false);
|
|
goto full_redraw;
|
|
} else {
|
|
secondary_button_override = true;
|
|
print_alarm(&alarms[selected_index], SETTING,get_alarm_x(selected_index),get_alarm_y(selected_index));
|
|
secondary_button_override = false;
|
|
goto full_redraw;
|
|
}
|
|
break;
|
|
case SECONDARY_BUTTON_PRESSED:
|
|
break;
|
|
case KNOB_CHANGE: {
|
|
uint8_t new_index = static_cast<uint8_t>(static_cast<float>(get_knob_percentage())/100.0f*static_cast<float>(alarms.size()));
|
|
if (new_index != selected_index) {
|
|
if (selected_index < alarms.size()) {
|
|
print_alarm(&alarms[selected_index], NOT_SELECTED,get_alarm_x(selected_index),get_alarm_y(selected_index));
|
|
} else {
|
|
print_add_button(MAX_X/2,MAX_Y-14,20,NOT_SELECTED);
|
|
}
|
|
if (new_index >= alarms.size()) {
|
|
print_add_button(MAX_X/2,MAX_Y-14,20,SELECTED);
|
|
} else {
|
|
print_alarm(&alarms[new_index], SELECTED,get_alarm_x(new_index),get_alarm_y(new_index));
|
|
}
|
|
selected_index = new_index;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
multicore_fifo_drain(); // prevent event stucking
|
|
}
|
|
|
|
}
|
|
|
|
std::array<void (*)(), 3> page_functions = {config_page, time_page,
|
|
alarm_set_page};
|