smart_alarm/ui.cpp
2025-10-04 22:25:57 +02:00

282 lines
11 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 "pins.h"
#include "smart_alarm.h"
#include "pico/multicore.h"
#include "pico/time.h"
#include "sound.h"
#include "timezones.h"
#include "hardware/gpio.h"
#include "bell.h"
#include "link.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
}
}
[[noreturn]] 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
}
}
[[noreturn]] 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};
[[noreturn]] void alarm_gone_off_page() {
printf("alarm went off\n");
clear_display();
page_selected = 1;
secondary_button_override = false;
draw_ui_circle(ORANGE);
gc9a01_blit_buffer(&display,120-(bell_image.width/2),180-(bell_image.height/2),bell_image.width,bell_image.height,const_cast<uint8_t*>(bell_image.pixel_data),std::size(bell_image.pixel_data));
gpio_put(LED_PIN, true);
play_alarm_audio();
print_time(true);
multicore_fifo_drain();
while (true) {
{
uint32_t event;
if (multicore_fifo_pop_timeout_us(500000,&event)) {
if (event == PRIMARY_BUTTON_PRESSED) {
stop_alarm_audio();
gpio_put(LED_PIN, false);
clear_display();
if (get_core_num()) {
asm volatile (
"ldr r3, %[stack_top] \n\t"
"mov sp, r3 \n\t"
:
: [stack_top] "m" (__StackOneTop)
: "r3"
);// clear the stack to prevent stack overflow from circular function calling
time_page();
} else {
multicore_launch_core1(&time_page);
}
}
}
multicore_fifo_drain();
}
print_time();
}
}