#include #include #include #include #include #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(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 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((get_knob_percentage() / 100.0f) * options.size()), static_cast(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 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( roundf(static_cast(7) / static_cast(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_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(); } } void alarm_set_page() { draw_ui_circle(BLUE); } std::array page_functions = {config_page, time_page, alarm_set_page};