#include "draw.h" #include "config.h" #include "game.h" #include "font.h" #include "state.h" #include "driver/ledc.h" #include "driver/i2c.h" #include "freertos/FreeRTOS.h" #include #include const int menu_slot[NUM_MENUS] = {0, 1, 2, 7}; const char *setting_names[NUM_SETTINGS] = { "LED BRIGHTNESS", "STARTING LIFE", "NUM OPPONENTS", "BLE", "RESET", "RESET ALL", "GAME ID", "PLAYER NAME", }; #define LEDC_TIMER_SEL LEDC_TIMER_0 #define LEDC_MODE_ LEDC_LOW_SPEED_MODE #define LEDC_DUTY_RES_ LEDC_TIMER_8_BIT #define LEDC_FREQ_HZ_ 5000 // ── LED ─────────────────────────────────────────────────────────────────────── void led_init(void) { ledc_timer_config_t timer = { .speed_mode = LEDC_MODE_, .timer_num = LEDC_TIMER_SEL, .duty_resolution = LEDC_DUTY_RES_, .freq_hz = LEDC_FREQ_HZ_, .clk_cfg = LEDC_AUTO_CLK, }; ledc_timer_config(&timer); ledc_channel_config_t ch[] = { {.gpio_num=LED_R_GPIO,.speed_mode=LEDC_MODE_,.channel=LEDC_CHANNEL_0,.timer_sel=LEDC_TIMER_SEL,.duty=0,.hpoint=0}, {.gpio_num=LED_G_GPIO,.speed_mode=LEDC_MODE_,.channel=LEDC_CHANNEL_1,.timer_sel=LEDC_TIMER_SEL,.duty=0,.hpoint=0}, {.gpio_num=LED_B_GPIO,.speed_mode=LEDC_MODE_,.channel=LEDC_CHANNEL_2,.timer_sel=LEDC_TIMER_SEL,.duty=0,.hpoint=0}, }; for (int i = 0; i < 3; i++) ledc_channel_config(&ch[i]); } void led_update_for_count(int count, uint8_t scale) { int c = count < 0 ? 0 : count > 80 ? 80 : count; uint8_t r, g, b; if (c <= 40) { int cc = c < 10 ? 10 : c; r = (uint8_t)((uint32_t)(40-cc)*scale*g_led_max/30/255); g = (uint8_t)((uint32_t)(cc-10)*scale*g_led_max/30/255); b = 0; } else { r = 0; g = (uint8_t)((uint32_t)(80-c)*g_led_max/40); b = (uint8_t)((uint32_t)(c-40)*g_led_max/40); } ledc_set_duty(LEDC_MODE_, LEDC_CHANNEL_0, r); ledc_update_duty(LEDC_MODE_, LEDC_CHANNEL_0); ledc_set_duty(LEDC_MODE_, LEDC_CHANNEL_1, g); ledc_update_duty(LEDC_MODE_, LEDC_CHANNEL_1); ledc_set_duty(LEDC_MODE_, LEDC_CHANNEL_2, b); ledc_update_duty(LEDC_MODE_, LEDC_CHANNEL_2); } // ── OLED ────────────────────────────────────────────────────────────────────── static void oled_write(const uint8_t *buf, size_t len) { i2c_master_write_to_device(I2C_PORT, OLED_ADDR, buf, len, pdMS_TO_TICKS(100)); } static void oled_cmd(uint8_t c) { uint8_t b[2]={0x00,c}; oled_write(b,2); } static void oled_write_page(int p, const uint8_t *row); void oled_init(void) { static const uint8_t seq[] = { 0xAE,0xD5,0x80,0xA8,0x3F,0xD3,0x00,0x40,0x8D,0x14,0x20,0x00, 0xA1,0xC8,0xDA,0x12,0x81,0xCF,0xD9,0xF1,0xDB,0x40,0xA4,0xA6,0xAF, }; for (int i = 0; i < (int)sizeof(seq); i++) oled_cmd(seq[i]); } void oled_clear(void) { uint8_t row[OLED_WIDTH]; memset(row, 0, sizeof(row)); for (int p = 0; p < OLED_PAGES; p++) oled_write_page(p, row); } static void oled_write_page(int p, const uint8_t *row) { oled_cmd(0x21); oled_cmd(0); oled_cmd(127); oled_cmd(0x22); oled_cmd(p); oled_cmd(p); uint8_t buf[OLED_WIDTH+1]; buf[0]=0x40; memcpy(buf+1, row, OLED_WIDTH); oled_write(buf, sizeof(buf)); } void oled_draw_header(void) { uint8_t pages[HEADER_PAGES][OLED_WIDTH]; memset(pages, 0, sizeof(pages)); for (int seg = 0; seg < NUM_MENUS; seg++) { uint8_t bg = (seg == g_active_menu) ? 0x00 : 0xFF; int sx = menu_slot[seg] * SEG_W; const uint8_t (*icon)[ICON_W] = (seg == MENU_CMDR && g_ble_enabled) ? icon_net : icons[seg]; for (int col = 0; col < ICON_W; col++) { pages[0][sx+col] = bg ^ icon[0][col]; pages[1][sx+col] = bg ^ icon[1][col]; } } for (int p = 0; p < HEADER_PAGES; p++) oled_write_page(p, pages[p]); } void oled_draw_centered(const char *str) { int len = strlen(str); int start = (OLED_WIDTH - len * CHAR_WIDTH) / 2; if (start < 0) start = 0; for (int p = HEADER_PAGES; p < OLED_PAGES; p++) { uint8_t row[OLED_WIDTH]; memset(row, 0, sizeof(row)); if (p >= START_PAGE && p < START_PAGE + CHAR_PAGES) { int char_page = p - START_PAGE; for (int i = 0; i < len; i++) { char c = str[i]; if (c < FONT_BASE || c > FONT_MAX) c = ' '; const uint8_t *glyph = font5x8[(uint8_t)c - FONT_BASE]; int col = start + i * CHAR_WIDTH; for (int sc = 0; sc < 5*SCALE; sc++) { int x = col + sc; if (x < 0 || x >= OLED_WIDTH) continue; uint8_t font_col = glyph[sc/SCALE]; uint8_t pixel_byte = 0; for (int b = 0; b < 8; b++) { int src_row = (char_page*8+b)/SCALE; if (src_row < 8 && ((font_col >> src_row) & 1)) pixel_byte |= (1<= base && 8*p+r < base+h) mask |= (1< FONT_MAX) c = ' '; const uint8_t *glyph = font5x8[c - FONT_BASE]; for (int sc = 0; sc < 5; sc++, x++) { for (int g = 0; g < 8; g++) { if ((glyph[sc] >> g) & 1) { int row = base + 1 + g; if (row < content_h) { if (inv) pages[row/8][x] &= ~(1 << (row%8)); else pages[row/8][x] |= (1 << (row%8)); } } } } x++; } return x; } // ── Core list renderer ──────────────────────────────────────────────────────── void oled_draw_rows(const oled_row_t *rows, int count, int active) { const int content_h = (OLED_PAGES - HEADER_PAGES) * 8; const int entry_h = 9; const int max_full = content_h / entry_h; int start = active - max_full / 2; if (start < 0) start = 0; if (start + max_full > count) start = count - max_full; if (start < 0) start = 0; uint8_t pages[OLED_PAGES - HEADER_PAGES][OLED_WIDTH]; memset(pages, 0, sizeof(pages)); for (int ei = 0; ei < max_full+1 && (start+ei) < count; ei++) { int entry = start + ei; int inv = (entry == active); int base = ei * entry_h; if (inv) pxbuf_fill(pages, content_h, base, entry_h); if (rows[entry].label && rows[entry].label[0]) pxbuf_str(pages, content_h, base, 2, rows[entry].label, inv); if (rows[entry].value[0]) { int vlen = (int)strlen(rows[entry].value); int rx = OLED_WIDTH - 2 - vlen*6; if (rx < 0) rx = 0; pxbuf_str(pages, content_h, base, rx, rows[entry].value, inv); if (inv && rows[entry].cursor >= 0 && rows[entry].cursor < vlen) { int cursor_x = rx + rows[entry].cursor * 6; for (int p = base/8; p <= (base+entry_h-1)/8 && p < content_h/8; p++) { uint8_t mask = row_mask(p, base, entry_h); for (int j = 0; j < 6 && cursor_x+j < OLED_WIDTH; j++) pages[p][cursor_x+j] &= ~mask; } char one[2] = { rows[entry].value[rows[entry].cursor], 0 }; pxbuf_str(pages, content_h, base, cursor_x, one, 0); } } } for (int p = 0; p < OLED_PAGES - HEADER_PAGES; p++) oled_write_page(HEADER_PAGES + p, pages[p]); } // ── Menu draw functions (thin wrappers that build rows and call oled_draw_rows) ── void oled_draw_list(const char **labels, const int *values, int count, int active) { oled_row_t rows[8]; int n = count < 8 ? count : 8; for (int i = 0; i < n; i++) { rows[i].label = labels[i]; rows[i].cursor = -1; snprintf(rows[i].value, sizeof(rows[i].value), "%d", values[i]); } oled_draw_rows(rows, n, active); } void oled_draw_players(void) { int total = MAX_BLE_PEERS + 1 + (!g_ble_enabled ? 1 : 0); oled_row_t rows[MAX_BLE_PEERS + 2]; rows[0].label = g_player_name; rows[0].cursor = -1; snprintf(rows[0].value, sizeof(rows[0].value), g_eliminated ? "ELIM" : "%d/%d", g_life, g_counters[0]); for (int pi = 0; pi < MAX_BLE_PEERS; pi++) { rows[pi+1].cursor = -1; if (g_peers[pi].active) { rows[pi+1].label = g_peers[pi].name; snprintf(rows[pi+1].value, sizeof(rows[pi+1].value), g_peers[pi].eliminated ? "ELIM" : "%d/%d/%d", g_peers[pi].life, g_peers[pi].poison, g_cmdr_damage[pi]); } else { rows[pi+1].label = ""; rows[pi+1].value[0] = 0; } } if (!g_ble_enabled) { rows[MAX_BLE_PEERS+1].label = "BLE OFF"; rows[MAX_BLE_PEERS+1].value[0] = 0; rows[MAX_BLE_PEERS+1].cursor = -1; } oled_draw_rows(rows, total, g_active_player); } void oled_draw_settings(void) { oled_row_t rows[NUM_SETTINGS]; for (int i = 0; i < NUM_SETTINGS; i++) { rows[i].label = setting_names[i]; rows[i].cursor = -1; } snprintf(rows[SET_BRIGHTNESS].value, sizeof(rows[0].value), "%d%%", g_brightness_pct); snprintf(rows[SET_START_LIFE].value, sizeof(rows[0].value), "%d", start_life_opts[g_start_life_index]); snprintf(rows[SET_NUM_OPP].value, sizeof(rows[0].value), "%d", g_num_opponents); memcpy(rows[SET_PLAYER_NAME].value, g_player_name, PLAYER_NAME_LEN); rows[SET_PLAYER_NAME].value[PLAYER_NAME_LEN] = 0; rows[SET_PLAYER_NAME].cursor = g_name_cursor; snprintf(rows[SET_BLE].value, sizeof(rows[0].value), "%s", g_ble_enabled ? "ON" : "OFF"); snprintf(rows[SET_GAME_ID].value, sizeof(rows[0].value), "%02X%02X", g_game_id[0], g_game_id[1]); rows[SET_GAME_ID].cursor = g_game_id_cursor; snprintf(rows[SET_RESET].value, sizeof(rows[0].value), "---"); snprintf(rows[SET_RESET_ALL].value, sizeof(rows[0].value), "---"); oled_draw_rows(rows, NUM_SETTINGS, g_active_setting); }