diff --git a/examples/companion_radio/ui-new/Emojisprites.h b/examples/companion_radio/ui-new/Emojisprites.h index 14b189db..8ae28c98 100644 --- a/examples/companion_radio/ui-new/Emojisprites.h +++ b/examples/companion_radio/ui-new/Emojisprites.h @@ -3,7 +3,7 @@ // Emoji sprites for e-ink display - dual size // Large (12x12) for compose/picker, Small (10x10) for channel view // MSB-first, 2 bytes per row -// 65 total emoji: joy/thumbsup/frown first, then 43 original, then 19 new +// 76 total emoji: joy/thumbsup/frown first, then 43 original, then 19 new, then 11 newest #include #ifdef ESP32 @@ -331,25 +331,29 @@ static const uint8_t emoji_lg_signal_strength[] PROGMEM = { static const uint8_t* const EMOJI_SPRITES_LG[] PROGMEM = { - emoji_lg_joy, emoji_lg_thumbsup, emoji_lg_frown, + // Faces/emotion first + emoji_lg_joy, emoji_lg_frown, emoji_lg_loudly_crying, + emoji_lg_grimace, emoji_lg_zany_face, emoji_lg_cowboy, + // Thumbsup + heart + emoji_lg_thumbsup, emoji_lg_heart, + // Everything else in original relative order emoji_lg_wireless, emoji_lg_infinity, emoji_lg_trex, emoji_lg_skull, emoji_lg_cross, emoji_lg_lightning, emoji_lg_tophat, emoji_lg_motorcycle, emoji_lg_seedling, emoji_lg_flag_au, emoji_lg_umbrella, emoji_lg_nazar, emoji_lg_globe, emoji_lg_radioactive, emoji_lg_cow, - emoji_lg_alien, emoji_lg_invader, emoji_lg_dagger, emoji_lg_grimace, + emoji_lg_alien, emoji_lg_invader, emoji_lg_dagger, emoji_lg_mountain, emoji_lg_end_arrow, emoji_lg_hollow_circle, emoji_lg_dragon, emoji_lg_globe_meridians, - emoji_lg_eggplant, emoji_lg_shield, emoji_lg_goggles, emoji_lg_lizard, emoji_lg_zany_face, + emoji_lg_eggplant, emoji_lg_shield, emoji_lg_goggles, emoji_lg_lizard, emoji_lg_kangaroo, emoji_lg_feather, emoji_lg_bright, emoji_lg_part_alt, emoji_lg_motorboat, - emoji_lg_domino, emoji_lg_satellite, emoji_lg_customs, emoji_lg_cowboy, emoji_lg_wheel, + emoji_lg_domino, emoji_lg_satellite, emoji_lg_customs, emoji_lg_wheel, emoji_lg_koala, emoji_lg_control_knobs, emoji_lg_peach, emoji_lg_racing_car, emoji_lg_mouse, emoji_lg_mushroom, emoji_lg_biohazard, emoji_lg_panda, emoji_lg_anger, emoji_lg_dragon_face, emoji_lg_pager, emoji_lg_bee, emoji_lg_bulb, emoji_lg_cat, emoji_lg_fleur, emoji_lg_moon, emoji_lg_coffee, emoji_lg_tooth, emoji_lg_pretzel, emoji_lg_abacus, emoji_lg_moai, emoji_lg_tipping, emoji_lg_hedgehog, - emoji_lg_loudly_crying, emoji_lg_heart, emoji_lg_diamond_suit, - emoji_lg_spade_suit, emoji_lg_pizza, emoji_lg_four_leaf_clover, - emoji_lg_cloud, emoji_lg_rocket, emoji_lg_passport_control, - emoji_lg_eight_spoked_asterisk, emoji_lg_signal_strength, + emoji_lg_diamond_suit, emoji_lg_spade_suit, emoji_lg_pizza, emoji_lg_four_leaf_clover, + emoji_lg_cloud, emoji_lg_rocket, emoji_lg_passport_control, + emoji_lg_eight_spoked_asterisk, emoji_lg_signal_strength, }; // ======== SMALL 10x10 SPRITES ======== @@ -614,114 +618,114 @@ static const uint8_t emoji_sm_signal_strength[] PROGMEM = { }; static const uint8_t* const EMOJI_SPRITES_SM[] PROGMEM = { - emoji_sm_joy, emoji_sm_thumbsup, emoji_sm_frown, + // Faces/emotion first + emoji_sm_joy, emoji_sm_frown, emoji_sm_loudly_crying, + emoji_sm_grimace, emoji_sm_zany_face, emoji_sm_cowboy, + // Thumbsup + heart + emoji_sm_thumbsup, emoji_sm_heart, + // Everything else in original relative order emoji_sm_wireless, emoji_sm_infinity, emoji_sm_trex, emoji_sm_skull, emoji_sm_cross, emoji_sm_lightning, emoji_sm_tophat, emoji_sm_motorcycle, emoji_sm_seedling, emoji_sm_flag_au, emoji_sm_umbrella, emoji_sm_nazar, emoji_sm_globe, emoji_sm_radioactive, emoji_sm_cow, - emoji_sm_alien, emoji_sm_invader, emoji_sm_dagger, emoji_sm_grimace, + emoji_sm_alien, emoji_sm_invader, emoji_sm_dagger, emoji_sm_mountain, emoji_sm_end_arrow, emoji_sm_hollow_circle, emoji_sm_dragon, emoji_sm_globe_meridians, - emoji_sm_eggplant, emoji_sm_shield, emoji_sm_goggles, emoji_sm_lizard, emoji_sm_zany_face, + emoji_sm_eggplant, emoji_sm_shield, emoji_sm_goggles, emoji_sm_lizard, emoji_sm_kangaroo, emoji_sm_feather, emoji_sm_bright, emoji_sm_part_alt, emoji_sm_motorboat, - emoji_sm_domino, emoji_sm_satellite, emoji_sm_customs, emoji_sm_cowboy, emoji_sm_wheel, + emoji_sm_domino, emoji_sm_satellite, emoji_sm_customs, emoji_sm_wheel, emoji_sm_koala, emoji_sm_control_knobs, emoji_sm_peach, emoji_sm_racing_car, emoji_sm_mouse, emoji_sm_mushroom, emoji_sm_biohazard, emoji_sm_panda, emoji_sm_anger, emoji_sm_dragon_face, emoji_sm_pager, emoji_sm_bee, emoji_sm_bulb, emoji_sm_cat, emoji_sm_fleur, emoji_sm_moon, emoji_sm_coffee, emoji_sm_tooth, emoji_sm_pretzel, emoji_sm_abacus, emoji_sm_moai, emoji_sm_tipping, emoji_sm_hedgehog, - emoji_sm_loudly_crying, - emoji_sm_heart, - emoji_sm_diamond_suit, - emoji_sm_spade_suit, - emoji_sm_pizza, - emoji_sm_four_leaf_clover, - emoji_sm_cloud, - emoji_sm_rocket, - emoji_sm_passport_control, - emoji_sm_eight_spoked_asterisk, - emoji_sm_signal_strength, + emoji_sm_diamond_suit, emoji_sm_spade_suit, emoji_sm_pizza, emoji_sm_four_leaf_clover, + emoji_sm_cloud, emoji_sm_rocket, emoji_sm_passport_control, + emoji_sm_eight_spoked_asterisk, emoji_sm_signal_strength, }; // ---- Codepoint lookup for UTF-8 conversion ---- struct EmojiCodepoint { uint32_t cp; uint32_t cp2; uint8_t escape; }; static const EmojiCodepoint EMOJI_CODEPOINTS[EMOJI_COUNT] = { + // Faces/emotion first { 0x1F602, 0x0000, 0x80 }, // joy - { 0x1F44D, 0x0000, 0x81 }, // thumbsup - { 0x2639, 0x0000, 0x82 }, // frown - { 0x1F6DC, 0x0000, 0x83 }, // wireless - { 0x267E, 0x0000, 0x84 }, // infinity - { 0x1F996, 0x0000, 0x85 }, // trex - { 0x2620, 0x0000, 0x86 }, // skull - { 0x271D, 0x0000, 0x87 }, // cross - { 0x26A1, 0x0000, 0x88 }, // lightning - { 0x1F3A9, 0x0000, 0x89 }, // tophat - { 0x1F3CD, 0x0000, 0x8A }, // motorcycle - { 0x1F331, 0x0000, 0x8B }, // seedling - { 0x1F1E6, 0x1F1FA, 0x8C }, // flag_au - { 0x2602, 0x0000, 0x8D }, // umbrella - { 0x1F9FF, 0x0000, 0x8E }, // nazar - { 0x1F30F, 0x0000, 0x8F }, // globe - { 0x2622, 0x0000, 0x90 }, // radioactive - { 0x1F404, 0x0000, 0x91 }, // cow - { 0x1F47D, 0x0000, 0x92 }, // alien - { 0x1F47E, 0x0000, 0x93 }, // invader - { 0x1F5E1, 0x0000, 0x94 }, // dagger - { 0x1F62C, 0x0000, 0x95 }, // grimace - { 0x26F0, 0x0000, 0x96 }, // mountain - { 0x1F51A, 0x0000, 0x97 }, // end_arrow - { 0x2B55, 0x0000, 0x98 }, // hollow_circle - { 0x1F409, 0x0000, 0x99 }, // dragon - { 0x1F310, 0x0000, 0x9A }, // globe_meridians - { 0x1F346, 0x0000, 0x9B }, // eggplant - { 0x1F6E1, 0x0000, 0x9C }, // shield - { 0x1F97D, 0x0000, 0x9D }, // goggles - { 0x1F98E, 0x0000, 0x9E }, // lizard - { 0x1F92A, 0x0000, 0x9F }, // zany_face - { 0x1F998, 0x0000, 0xA0 }, // kangaroo - { 0x1FAB6, 0x0000, 0xA1 }, // feather - { 0x1F506, 0x0000, 0xA2 }, // bright - { 0x303D, 0x0000, 0xA3 }, // part_alt - { 0x1F6E5, 0x0000, 0xA4 }, // motorboat - { 0x1F030, 0x0000, 0xA5 }, // domino - { 0x1F4E1, 0x0000, 0xA6 }, // satellite - { 0x1F6C3, 0x0000, 0xA7 }, // customs - { 0x1F920, 0x0000, 0xA8 }, // cowboy - { 0x1F6DE, 0x0000, 0xA9 }, // wheel - { 0x1F428, 0x0000, 0xAA }, // koala - { 0x1F39B, 0x0000, 0xAB }, // control_knobs - { 0x1F351, 0x0000, 0xAC }, // peach - { 0x1F3CE, 0x0000, 0xAD }, // racing_car - { 0x1F42D, 0x0000, 0xAE }, // mouse - { 0x1F344, 0x0000, 0xAF }, // mushroom - { 0x2623, 0x0000, 0xB0 }, // biohazard - { 0x1F43C, 0x0000, 0xB1 }, // panda - { 0x1F4A2, 0x0000, 0xB2 }, // anger - { 0x1F432, 0x0000, 0xB3 }, // dragon_face - { 0x1F4DF, 0x0000, 0xB4 }, // pager - { 0x1F41D, 0x0000, 0xB5 }, // bee - { 0x1F4A1, 0x0000, 0xB6 }, // bulb - { 0x1F431, 0x0000, 0xB7 }, // cat - { 0x269C, 0x0000, 0xB8 }, // fleur - { 0x1F314, 0x0000, 0xB9 }, // moon - { 0x2615, 0x0000, 0xBA }, // coffee - { 0x1F9B7, 0x0000, 0xBB }, // tooth - { 0x1F968, 0x0000, 0xBC }, // pretzel - { 0x1F9EE, 0x0000, 0xBD }, // abacus - { 0x1F5FF, 0x0000, 0xBE }, // moai - { 0x1F481, 0x0000, 0xBF }, // tipping - { 0x1F994, 0x0000, 0xC0 }, // hedgehog - { 0x1F62D , 0x0000 , 0xC1 }, // loudly_crying - { 0x2665 , 0x0000 , 0xC2 }, // heart - { 0x2666 , 0x0000 , 0xC3 }, // diamond_suit - { 0x2660 , 0x0000 , 0xC4 }, // spade_suit - { 0x1F355 , 0x0000 , 0xC5 }, // pizza - { 0x1F340 , 0x0000 , 0xC6 }, // four_leaf_clover - { 0x2601 , 0x0000 , 0xC7 }, // cloud - { 0x1F680 , 0x0000 , 0xC8 }, // rocket - { 0x1F6C2 , 0x0000 , 0xC9 }, // passport_control - { 0x2733 , 0x0000 , 0xCA }, // eight_spoked_asterisk - { 0x1F4F6 , 0x0000 , 0xCB }, // signal_strength + { 0x2639, 0x0000, 0x81 }, // frown + { 0x1F62D, 0x0000, 0x82 }, // loudly_crying + { 0x1F62C, 0x0000, 0x83 }, // grimace + { 0x1F92A, 0x0000, 0x84 }, // zany_face + { 0x1F920, 0x0000, 0x85 }, // cowboy + // Thumbsup + heart + { 0x1F44D, 0x0000, 0x86 }, // thumbsup + { 0x2665, 0x0000, 0x87 }, // heart + // Everything else in original relative order + { 0x1F6DC, 0x0000, 0x88 }, // wireless + { 0x267E, 0x0000, 0x89 }, // infinity + { 0x1F996, 0x0000, 0x8A }, // trex + { 0x2620, 0x0000, 0x8B }, // skull + { 0x271D, 0x0000, 0x8C }, // cross + { 0x26A1, 0x0000, 0x8D }, // lightning + { 0x1F3A9, 0x0000, 0x8E }, // tophat + { 0x1F3CD, 0x0000, 0x8F }, // motorcycle + { 0x1F331, 0x0000, 0x90 }, // seedling + { 0x1F1E6, 0x1F1FA, 0x91 }, // flag_au + { 0x2602, 0x0000, 0x92 }, // umbrella + { 0x1F9FF, 0x0000, 0x93 }, // nazar + { 0x1F30F, 0x0000, 0x94 }, // globe + { 0x2622, 0x0000, 0x95 }, // radioactive + { 0x1F404, 0x0000, 0x96 }, // cow + { 0x1F47D, 0x0000, 0x97 }, // alien + { 0x1F47E, 0x0000, 0x98 }, // invader + { 0x1F5E1, 0x0000, 0x99 }, // dagger + { 0x26F0, 0x0000, 0x9A }, // mountain + { 0x1F51A, 0x0000, 0x9B }, // end_arrow + { 0x2B55, 0x0000, 0x9C }, // hollow_circle + { 0x1F409, 0x0000, 0x9D }, // dragon + { 0x1F310, 0x0000, 0x9E }, // globe_meridians + { 0x1F346, 0x0000, 0x9F }, // eggplant + { 0x1F6E1, 0x0000, 0xA0 }, // shield + { 0x1F97D, 0x0000, 0xA1 }, // goggles + { 0x1F98E, 0x0000, 0xA2 }, // lizard + { 0x1F998, 0x0000, 0xA3 }, // kangaroo + { 0x1FAB6, 0x0000, 0xA4 }, // feather + { 0x1F506, 0x0000, 0xA5 }, // bright + { 0x303D, 0x0000, 0xA6 }, // part_alt + { 0x1F6E5, 0x0000, 0xA7 }, // motorboat + { 0x1F030, 0x0000, 0xA8 }, // domino + { 0x1F4E1, 0x0000, 0xA9 }, // satellite + { 0x1F6C3, 0x0000, 0xAA }, // customs + { 0x1F6DE, 0x0000, 0xAB }, // wheel + { 0x1F428, 0x0000, 0xAC }, // koala + { 0x1F39B, 0x0000, 0xAD }, // control_knobs + { 0x1F351, 0x0000, 0xAE }, // peach + { 0x1F3CE, 0x0000, 0xAF }, // racing_car + { 0x1F42D, 0x0000, 0xB0 }, // mouse + { 0x1F344, 0x0000, 0xB1 }, // mushroom + { 0x2623, 0x0000, 0xB2 }, // biohazard + { 0x1F43C, 0x0000, 0xB3 }, // panda + { 0x1F4A2, 0x0000, 0xB4 }, // anger + { 0x1F432, 0x0000, 0xB5 }, // dragon_face + { 0x1F4DF, 0x0000, 0xB6 }, // pager + { 0x1F41D, 0x0000, 0xB7 }, // bee + { 0x1F4A1, 0x0000, 0xB8 }, // bulb + { 0x1F431, 0x0000, 0xB9 }, // cat + { 0x269C, 0x0000, 0xBA }, // fleur + { 0x1F314, 0x0000, 0xBB }, // moon + { 0x2615, 0x0000, 0xBC }, // coffee + { 0x1F9B7, 0x0000, 0xBD }, // tooth + { 0x1F968, 0x0000, 0xBE }, // pretzel + { 0x1F9EE, 0x0000, 0xBF }, // abacus + { 0x1F5FF, 0x0000, 0xC0 }, // moai + { 0x1F481, 0x0000, 0xC1 }, // tipping + { 0x1F994, 0x0000, 0xC2 }, // hedgehog + { 0x2666, 0x0000, 0xC3 }, // diamond_suit + { 0x2660, 0x0000, 0xC4 }, // spade_suit + { 0x1F355, 0x0000, 0xC5 }, // pizza + { 0x1F340, 0x0000, 0xC6 }, // four_leaf_clover + { 0x2601, 0x0000, 0xC7 }, // cloud + { 0x1F680, 0x0000, 0xC8 }, // rocket + { 0x1F6C2, 0x0000, 0xC9 }, // passport_control + { 0x2733, 0x0000, 0xCA }, // eight_spoked_asterisk + { 0x1F4F6, 0x0000, 0xCB }, // signal_strength }; // ---- Helper functions ---- @@ -731,7 +735,7 @@ static const EmojiCodepoint EMOJI_CODEPOINTS[EMOJI_COUNT] = { struct EmojiAlias { uint32_t cp; uint8_t escape; }; #define EMOJI_ALIAS_COUNT 1 static const EmojiAlias EMOJI_ALIASES[EMOJI_ALIAS_COUNT] = { - { 0x1F08E, 0xA5 }, // domino tile (MWD node signifier) -> domino sprite + { 0x1F08E, 0xA8 }, // domino tile (MWD node signifier) -> domino sprite }; static uint32_t emojiDecodeUtf8(const uint8_t* s, int remaining, int* bytes_consumed) { diff --git a/examples/companion_radio/ui-new/emojipicker.h b/examples/companion_radio/ui-new/emojipicker.h index aa0ced68..368b3cc3 100644 --- a/examples/companion_radio/ui-new/emojipicker.h +++ b/examples/companion_radio/ui-new/emojipicker.h @@ -12,80 +12,83 @@ #define EMOJI_PICKER_TOTAL_ROWS ((EMOJI_COUNT + EMOJI_PICKER_COLS - 1) / EMOJI_PICKER_COLS) static const char* EMOJI_LABELS[EMOJI_COUNT] = { + // Faces/emotion "Lol", // 0 joy - "Like", // 1 thumbsup - "Sad", // 2 frown - "WiFi", // 3 wireless - "Inf", // 4 infinity - "Rex", // 5 trex - "Skul", // 6 skull - "Cros", // 7 cross - "Bolt", // 8 lightning - "Hat", // 9 tophat - "Moto", // 10 motorcycle - "Leaf", // 11 seedling - "AU", // 12 flag_au - "Umbr", // 13 umbrella - "Eye", // 14 nazar - "Glob", // 15 globe - "Rad", // 16 radioactive - "Cow", // 17 cow - "ET", // 18 alien - "Inv", // 19 invader - "Dagr", // 20 dagger - "Grim", // 21 grimace - "Mtn", // 22 mountain - "End", // 23 end_arrow - "Ring", // 24 hollow_circle - "Drag", // 25 dragon - "Web", // 26 globe_meridians - "Eggp", // 27 eggplant - "Shld", // 28 shield - "Gogl", // 29 goggles - "Lzrd", // 30 lizard - "Zany", // 31 zany_face - "Roo", // 32 kangaroo - "Fthr", // 33 feather - "Sun", // 34 bright - "Wave", // 35 part_alt - "Boat", // 36 motorboat - "Domi", // 37 domino - "Dish", // 38 satellite - "Pass", // 39 customs - "Cowb", // 40 cowboy - "Whl", // 41 wheel - "Koal", // 42 koala - "Knob", // 43 control_knobs - "Pch", // 44 peach - "Race", // 45 racing_car - "Mous", // 46 mouse - "Shrm", // 47 mushroom - "Bio", // 48 biohazard - "Pnda", // 49 panda - "Bang", // 50 anger - "DrgF", // 51 dragon_face - "Pagr", // 52 pager - "Bee", // 53 bee - "Bulb", // 54 bulb - "Cat", // 55 cat - "Flur", // 56 fleur - "Moon", // 57 moon - "Cafe", // 58 coffee - "Toth", // 59 tooth - "Prtz", // 60 pretzel - "Abac", // 61 abacus - "Moai", // 62 moai - "Hiii", // 63 tipping - "Hedg", // 64 hedgehog - "Cry", // 65 loudly_crying - "Love", // 66 heart + "Sad", // 1 frown + "Cry", // 2 loudly_crying + "Grim", // 3 grimace + "Zany", // 4 zany_face + "Cowb", // 5 cowboy + // Thumbsup + heart + "Like", // 6 thumbsup + "Love", // 7 heart + // Everything else + "WiFi", // 8 wireless + "Inf", // 9 infinity + "Rex", // 10 trex + "Skul", // 11 skull + "Cros", // 12 cross + "Bolt", // 13 lightning + "Hat", // 14 tophat + "Moto", // 15 motorcycle + "Leaf", // 16 seedling + "AU", // 17 flag_au + "Umbr", // 18 umbrella + "Eye", // 19 nazar + "Glob", // 20 globe + "Rad", // 21 radioactive + "Cow", // 22 cow + "ET", // 23 alien + "Inv", // 24 invader + "Dagr", // 25 dagger + "Mtn", // 26 mountain + "End", // 27 end_arrow + "Ring", // 28 hollow_circle + "Drag", // 29 dragon + "Web", // 30 globe_meridians + "Eggp", // 31 eggplant + "Shld", // 32 shield + "Gogl", // 33 goggles + "Lzrd", // 34 lizard + "Roo", // 35 kangaroo + "Fthr", // 36 feather + "Sun", // 37 bright + "Wave", // 38 part_alt + "Boat", // 39 motorboat + "Domi", // 40 domino + "Dish", // 41 satellite + "Pass", // 42 customs + "Whl", // 43 wheel + "Koal", // 44 koala + "Knob", // 45 control_knobs + "Pch", // 46 peach + "Race", // 47 racing_car + "Mous", // 48 mouse + "Shrm", // 49 mushroom + "Bio", // 50 biohazard + "Pnda", // 51 panda + "Bang", // 52 anger + "DrgF", // 53 dragon_face + "Pagr", // 54 pager + "Bee", // 55 bee + "Bulb", // 56 bulb + "Cat", // 57 cat + "Flur", // 58 fleur + "Moon", // 59 moon + "Cafe", // 60 coffee + "Toth", // 61 tooth + "Prtz", // 62 pretzel + "Abac", // 63 abacus + "Moai", // 64 moai + "Hiii", // 65 tipping + "Hedg", // 66 hedgehog "Diam", // 67 diamond_suit "Spde", // 68 spade_suit "Piza", // 69 pizza "Luck", // 70 four_leaf_clover "Cld", // 71 cloud "Rckt", // 72 rocket - "Visa", // 73 passport_control + "HFC", // 73 passport_control "Star", // 74 eight_spoked_asterisk "Sig", // 75 signal_strength }; @@ -116,13 +119,23 @@ struct EmojiPicker { switch (key) { case 'w': case 'W': case 0xF2: - if (row > 0) cursor -= EMOJI_PICKER_COLS; + if (row > 0) { + cursor -= EMOJI_PICKER_COLS; + } else { + // Wrap to last row, same column + int target = (EMOJI_PICKER_TOTAL_ROWS - 1) * EMOJI_PICKER_COLS + col; + cursor = (target >= EMOJI_COUNT) ? EMOJI_COUNT - 1 : target; + } break; case 's': case 'S': case 0xF1: - if (cursor + EMOJI_PICKER_COLS < EMOJI_COUNT) + if (cursor + EMOJI_PICKER_COLS < EMOJI_COUNT) { cursor += EMOJI_PICKER_COLS; - else if (row < EMOJI_PICKER_TOTAL_ROWS - 1) + } else if (row < EMOJI_PICKER_TOTAL_ROWS - 1) { cursor = EMOJI_COUNT - 1; + } else { + // Wrap to first row, same column + cursor = col; + } break; case 'a': case 'A': if (cursor > 0) cursor--; diff --git a/examples/companion_radio/ui-new/virtualkeyboard.h b/examples/companion_radio/ui-new/virtualkeyboard.h index 2f2a5517..824c73f3 100644 --- a/examples/companion_radio/ui-new/virtualkeyboard.h +++ b/examples/companion_radio/ui-new/virtualkeyboard.h @@ -18,6 +18,7 @@ #include #include +#include "EmojiSprites.h" enum VKBStatus { VKB_EDITING, VKB_SUBMITTED, VKB_CANCELLED }; @@ -45,7 +46,8 @@ public: static const int MAX_TEXT = 140; VirtualKeyboard() : _status(VKB_CANCELLED), _purpose(VKB_CHANNEL_MSG), - _contextIdx(0), _textLen(0), _shifted(false), _symbols(false) { + _contextIdx(0), _textLen(0), _shifted(false), _symbols(false), + _emojiMode(false), _emojiScroll(0) { _text[0] = '\0'; _label[0] = '\0'; } @@ -56,6 +58,8 @@ public: _status = VKB_EDITING; _shifted = false; _symbols = false; + _emojiMode = false; + _emojiScroll = 0; _maxLen = (maxLen > 0 && maxLen < MAX_TEXT) ? maxLen : MAX_TEXT; strncpy(_label, label, sizeof(_label) - 1); @@ -90,13 +94,8 @@ public: display.setColor(DisplayDriver::LIGHT); display.drawRect(0, 10, 128, 18); // Border - display.setCursor(2, 12); - display.setColor(DisplayDriver::LIGHT); - - // Show text with cursor - char dispBuf[MAX_TEXT + 2]; - snprintf(dispBuf, sizeof(dispBuf), "%s_", _text); - display.print(dispBuf); + // Render text with inline emoji sprites + renderTextField(display); // Character count { @@ -111,6 +110,11 @@ public: // Separator display.drawRect(0, 30, 128, 1); + if (_emojiMode) { + renderEmojiGrid(display); + return; + } + // --- Draw keyboard rows --- const char* const* layout = getLayout(); @@ -183,6 +187,8 @@ public: bool handleTap(int vx, int vy) { if (_status != VKB_EDITING) return false; + if (_emojiMode) return handleEmojiTap(vx, vy); + // Check keyboard rows 0-2 const char* const* layout = getLayout(); @@ -253,6 +259,169 @@ private: char _label[40]; bool _shifted; bool _symbols; + bool _emojiMode; + int _emojiScroll; + + // Emoji grid constants (virtual coords) + static const int EMJ_COLS = 8; + static const int EMJ_CELL = 15; // 12px sprite + 3px gap + static const int EMJ_GRID_X = 4; + static const int EMJ_GRID_Y = 34; + static const int EMJ_VIS_ROWS = 5; + + int emojiTotalRows() const { return (EMOJI_COUNT + EMJ_COLS - 1) / EMJ_COLS; } + int emojiMaxScroll() const { int m = emojiTotalRows() - EMJ_VIS_ROWS; return m < 0 ? 0 : m; } + + void renderEmojiGrid(DisplayDriver& display) { + display.setTextSize(0); + + for (int vr = 0; vr < EMJ_VIS_ROWS; vr++) { + int absRow = _emojiScroll + vr; + if (absRow >= emojiTotalRows()) break; + + for (int col = 0; col < EMJ_COLS; col++) { + int idx = absRow * EMJ_COLS + col; + if (idx >= EMOJI_COUNT) break; + + int cx = EMJ_GRID_X + col * EMJ_CELL; + int cy = EMJ_GRID_Y + vr * EMJ_CELL; + + display.setColor(DisplayDriver::LIGHT); + const uint8_t* sprite = (const uint8_t*)pgm_read_ptr(&EMOJI_SPRITES_LG[idx]); + if (sprite) { + display.drawXbm(cx + 1, cy + 1, sprite, EMOJI_LG_W, EMOJI_LG_H); + } + } + } + + // Footer: [Back] [▲] page/total [▼] + int fy = EMJ_GRID_Y + EMJ_VIS_ROWS * EMJ_CELL + 2; + display.setColor(DisplayDriver::LIGHT); + display.drawRect(0, fy - 1, 128, 1); + + // Back button (inverted) + display.fillRect(4, fy + 1, 30, 12); + display.setColor(DisplayDriver::DARK); + int bw = display.getTextWidth("Back"); + display.setCursor(4 + (30 - bw) / 2, fy + 2); + display.print("Back"); + + display.setColor(DisplayDriver::LIGHT); + + // Scroll arrows (only if scrollable) + if (emojiTotalRows() > EMJ_VIS_ROWS) { + // Up arrow + if (_emojiScroll > 0) { + display.fillRect(50, fy + 1, 12, 12); + display.setColor(DisplayDriver::DARK); + display.setCursor(53, fy + 2); + display.print("^"); + display.setColor(DisplayDriver::LIGHT); + } + + // Page info + char pg[8]; + snprintf(pg, sizeof(pg), "%d/%d", _emojiScroll + 1, emojiMaxScroll() + 1); + int pw = display.getTextWidth(pg); + display.setCursor(75 - pw / 2, fy + 2); + display.print(pg); + + // Down arrow + if (_emojiScroll < emojiMaxScroll()) { + display.fillRect(90, fy + 1, 12, 12); + display.setColor(DisplayDriver::DARK); + display.setCursor(93, fy + 2); + display.print("v"); + display.setColor(DisplayDriver::LIGHT); + } + } + } + + bool handleEmojiTap(int vx, int vy) { + int fy = EMJ_GRID_Y + EMJ_VIS_ROWS * EMJ_CELL + 2; + + // Footer area + if (vy >= fy) { + if (vx >= 4 && vx < 34) { + // Back button + _emojiMode = false; + return true; + } + if (vx >= 50 && vx < 62 && _emojiScroll > 0) { + _emojiScroll--; + return true; + } + if (vx >= 90 && vx < 102 && _emojiScroll < emojiMaxScroll()) { + _emojiScroll++; + return true; + } + return true; // Consume tap in footer + } + + // Grid area + if (vy >= EMJ_GRID_Y && vy < EMJ_GRID_Y + EMJ_VIS_ROWS * EMJ_CELL) { + int col = (vx - EMJ_GRID_X) / EMJ_CELL; + int vr = (vy - EMJ_GRID_Y) / EMJ_CELL; + if (col < 0 || col >= EMJ_COLS || vr < 0 || vr >= EMJ_VIS_ROWS) return true; + + int idx = (_emojiScroll + vr) * EMJ_COLS + col; + if (idx >= 0 && idx < EMOJI_COUNT) { + insertEmoji(idx); + _emojiMode = false; + } + return true; + } + + return true; // Consume any tap while in emoji mode + } + + void insertEmoji(int idx) { + // Insert as UTF-8 directly (not escape bytes) so sent messages are valid + uint8_t utf8[8]; + int len = emojiEncodeUtf8(EMOJI_CODEPOINTS[idx].cp, utf8); + if (EMOJI_CODEPOINTS[idx].cp2 != 0) + len += emojiEncodeUtf8(EMOJI_CODEPOINTS[idx].cp2, utf8 + len); + if (_textLen + len > _maxLen) return; + memcpy(_text + _textLen, utf8, len); + _textLen += len; + _text[_textLen] = '\0'; + } + + // Render text field with inline emoji sprites (10×10) + void renderTextField(DisplayDriver& display) { + // Convert UTF-8 emoji to escape bytes for sprite lookup + char sanitized[MAX_TEXT + 1]; + emojiSanitize(_text, sanitized, sizeof(sanitized)); + + int x = 2; + int maxX = 124; + display.setColor(DisplayDriver::LIGHT); + display.setTextSize(0); + + for (int i = 0; sanitized[i] && x < maxX; i++) { + uint8_t b = (uint8_t)sanitized[i]; + if (b == EMOJI_PAD_BYTE) continue; + + if (isEmojiEscape(b)) { + const uint8_t* sprite = getEmojiSpriteSm(b); + if (sprite && x + EMOJI_SM_W < maxX) { + display.drawXbm(x, 14, sprite, EMOJI_SM_W, EMOJI_SM_H); + x += EMOJI_SM_W + 1; + } + } else { + char ch[2] = { (char)b, '\0' }; + display.setCursor(x, 12); + display.print(ch); + x += display.getTextWidth(ch); + } + } + + // Blinking cursor + if (x < maxX) { + display.setCursor(x, 12); + display.print("_"); + } + } // Layout constants (virtual coords) static const int KEY_W = 11; @@ -261,7 +430,7 @@ private: static const int KEY_START_Y = 34; // Key layouts — rows 0-2 as char arrays - // Special: ^ = shift, < = backspace, # = symbols, > = enter, ~ = space + // Special: ^ = shift, < = backspace, \x01 = sym toggle, \x02 = emoji, > = enter, ~ = space const char* const* getLayout() const { static const char* const lower[3] = { "qwertyuiop", "asdfghjkl", "^zxcvbnm<" }; static const char* const upper[3] = { "QWERTYUIOP", "ASDFGHJKL", "^ZXCVBNM<" }; @@ -269,26 +438,22 @@ private: return _symbols ? syms : (_shifted ? upper : lower); } - // Row 4: variable-width keys [#/ABC] [,] [SPACE] [.] [Enter] + // Row 4: variable-width keys [#/ABC] [,] [$] [SPACE] [.] [Enter] // Defined by physical zones, not the char-array approach struct R4Key { int x; int w; char ch; const char* label; }; void drawRow4(DisplayDriver& display, int y) { - // # or ABC toggle: x=4, w=20 - // comma: x=26, w=11 - // space: x=39, w=50 - // period: x=91, w=11 - // enter: x=104, w=20 const R4Key keys[] = { { 4, 20, '\x01', _symbols ? "ABC" : "123" }, { 26, 11, ',', "," }, - { 39, 50, '~', "space" }, + { 39, 11, '\x02', "$" }, + { 52, 37, '~', "space" }, { 91, 11, '.', "." }, { 104, 20, '>', "Send" } }; - for (int i = 0; i < 5; i++) { - bool special = (keys[i].ch == '\x01' || keys[i].ch == '>'); + for (int i = 0; i < 6; i++) { + bool special = (keys[i].ch == '\x01' || keys[i].ch == '>' || keys[i].ch == '\x02'); if (special) { display.setColor(DisplayDriver::LIGHT); display.fillRect(keys[i].x, y + 1, keys[i].w, KEY_H - 1); @@ -312,11 +477,12 @@ private: const R4Key keys[] = { { 4, 20, '\x01', nullptr }, { 26, 11, ',', nullptr }, - { 39, 50, '~', nullptr }, + { 39, 11, '\x02', nullptr }, + { 52, 37, '~', nullptr }, { 91, 11, '.', nullptr }, { 104, 20, '>', nullptr } }; - for (int i = 0; i < 5; i++) { + for (int i = 0; i < 6; i++) { if (vx >= keys[i].x && vx < keys[i].x + keys[i].w) { processKey(keys[i].ch); return true; @@ -334,10 +500,17 @@ private: // Symbol/letter toggle _symbols = !_symbols; _shifted = false; + } else if (ch == '\x02') { + // Emoji picker toggle + _emojiMode = !_emojiMode; + _emojiScroll = 0; } else if (ch == '<') { - // Backspace + // Backspace — UTF-8 aware (walk back past continuation bytes 10xxxxxx) if (_textLen > 0) { _textLen--; + while (_textLen > 0 && ((uint8_t)_text[_textLen] & 0xC0) == 0x80) { + _textLen--; + } _text[_textLen] = '\0'; } } else if (ch == '>') {