mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-08 22:22:05 +00:00
Fn+e emote picker for freetext screen
This commit is contained in:
parent
0c1d49e254
commit
97eb03cb35
@ -20,6 +20,7 @@
|
|||||||
#define INPUT_BROKER_MSG_FN_SYMBOL_OFF 0xf2
|
#define INPUT_BROKER_MSG_FN_SYMBOL_OFF 0xf2
|
||||||
#define INPUT_BROKER_MSG_BLUETOOTH_TOGGLE 0xAA
|
#define INPUT_BROKER_MSG_BLUETOOTH_TOGGLE 0xAA
|
||||||
#define INPUT_BROKER_MSG_TAB 0x09
|
#define INPUT_BROKER_MSG_TAB 0x09
|
||||||
|
#define INPUT_BROKER_MSG_EMOTE_LIST 0x8F
|
||||||
|
|
||||||
typedef struct _InputEvent {
|
typedef struct _InputEvent {
|
||||||
const char *source;
|
const char *source;
|
||||||
|
@ -435,6 +435,7 @@ int32_t KbI2cBase::runOnce()
|
|||||||
case 0xaf: // fn+space INPUT_BROKER_MSG_SEND_PING
|
case 0xaf: // fn+space INPUT_BROKER_MSG_SEND_PING
|
||||||
case 0x8b: // fn+del INPUT_BROKEN_MSG_DISMISS_FRAME
|
case 0x8b: // fn+del INPUT_BROKEN_MSG_DISMISS_FRAME
|
||||||
case 0xAA: // fn+b INPUT_BROKER_MSG_BLUETOOTH_TOGGLE
|
case 0xAA: // fn+b INPUT_BROKER_MSG_BLUETOOTH_TOGGLE
|
||||||
|
case 0x8F: // fn+e INPUT_BROKER_MSG_EMOTE_LIST
|
||||||
// just pass those unmodified
|
// just pass those unmodified
|
||||||
e.inputEvent = ANYKEY;
|
e.inputEvent = ANYKEY;
|
||||||
e.kbchar = c;
|
e.kbchar = c;
|
||||||
|
@ -298,6 +298,10 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
|
|||||||
case CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE:
|
case CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE:
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
// If sending, block all input except global/system (handled above)
|
||||||
|
case CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER:
|
||||||
|
return handleEmotePickerInput(event);
|
||||||
|
|
||||||
case CANNED_MESSAGE_RUN_STATE_INACTIVE:
|
case CANNED_MESSAGE_RUN_STATE_INACTIVE:
|
||||||
if (isSelect) {
|
if (isSelect) {
|
||||||
// When inactive, call the onebutton shortpress instead. Activate module only on up/down
|
// When inactive, call the onebutton shortpress instead. Activate module only on up/down
|
||||||
@ -647,6 +651,12 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
|
|||||||
|
|
||||||
// ---- All hardware keys fall through to here (CardKB, physical, etc.) ----
|
// ---- All hardware keys fall through to here (CardKB, physical, etc.) ----
|
||||||
|
|
||||||
|
if (event->kbchar == INPUT_BROKER_MSG_EMOTE_LIST) {
|
||||||
|
runState = CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER;
|
||||||
|
requestFocus();
|
||||||
|
screen->forceDisplay();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// Confirm select (Enter)
|
// Confirm select (Enter)
|
||||||
bool isSelect = isSelectEvent(event);
|
bool isSelect = isSelectEvent(event);
|
||||||
if (isSelect) {
|
if (isSelect) {
|
||||||
@ -715,6 +725,47 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int CannedMessageModule::handleEmotePickerInput(const InputEvent *event)
|
||||||
|
{
|
||||||
|
int numEmotes = graphics::numEmotes;
|
||||||
|
bool isUp = isUpEvent(event);
|
||||||
|
bool isDown = isDownEvent(event);
|
||||||
|
bool isSelect = isSelectEvent(event);
|
||||||
|
|
||||||
|
// Scroll emote list
|
||||||
|
if (isUp && emotePickerIndex > 0) {
|
||||||
|
emotePickerIndex--;
|
||||||
|
screen->forceDisplay();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (isDown && emotePickerIndex < numEmotes - 1) {
|
||||||
|
emotePickerIndex++;
|
||||||
|
screen->forceDisplay();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Select emote: insert into freetext at cursor and return to freetext
|
||||||
|
if (isSelect) {
|
||||||
|
String label = graphics::emotes[emotePickerIndex].label;
|
||||||
|
String emoteInsert = label; // Just the text label, e.g., ":thumbsup:"
|
||||||
|
if (cursor == freetext.length()) {
|
||||||
|
freetext += emoteInsert;
|
||||||
|
} else {
|
||||||
|
freetext = freetext.substring(0, cursor) + emoteInsert + freetext.substring(cursor);
|
||||||
|
}
|
||||||
|
cursor += emoteInsert.length();
|
||||||
|
runState = CANNED_MESSAGE_RUN_STATE_FREETEXT;
|
||||||
|
screen->forceDisplay();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Cancel returns to freetext
|
||||||
|
if (event->inputEvent == static_cast<char>(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL)) {
|
||||||
|
runState = CANNED_MESSAGE_RUN_STATE_FREETEXT;
|
||||||
|
screen->forceDisplay();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool CannedMessageModule::handleSystemCommandInput(const InputEvent *event)
|
bool CannedMessageModule::handleSystemCommandInput(const InputEvent *event)
|
||||||
{
|
{
|
||||||
// Only respond to "ANYKEY" events for system keys
|
// Only respond to "ANYKEY" events for system keys
|
||||||
@ -997,7 +1048,8 @@ int32_t CannedMessageModule::runOnce()
|
|||||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
|
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
|
||||||
switch (this->payload) {
|
switch (this->payload) {
|
||||||
case 0x08: // backspace
|
case 0x08: // backspace
|
||||||
if (this->freetext.length() > 0 && this->highlight == 0x00) {
|
if (this->freetext.length() > 0) {
|
||||||
|
if (this->cursor > 0) {
|
||||||
if (this->cursor == this->freetext.length()) {
|
if (this->cursor == this->freetext.length()) {
|
||||||
this->freetext = this->freetext.substring(0, this->freetext.length() - 1);
|
this->freetext = this->freetext.substring(0, this->freetext.length() - 1);
|
||||||
} else {
|
} else {
|
||||||
@ -1006,6 +1058,7 @@ int32_t CannedMessageModule::runOnce()
|
|||||||
}
|
}
|
||||||
this->cursor--;
|
this->cursor--;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case INPUT_BROKER_MSG_TAB: // Tab key: handled by input handler
|
case INPUT_BROKER_MSG_TAB: // Tab key: handled by input handler
|
||||||
return 0;
|
return 0;
|
||||||
@ -1013,20 +1066,21 @@ int32_t CannedMessageModule::runOnce()
|
|||||||
case INPUT_BROKER_MSG_RIGHT:
|
case INPUT_BROKER_MSG_RIGHT:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (this->highlight != 0x00)
|
// Only insert ASCII printable characters (32–126)
|
||||||
break;
|
if (this->payload >= 32 && this->payload <= 126) {
|
||||||
if (this->cursor == this->freetext.length()) {
|
if (this->cursor == this->freetext.length()) {
|
||||||
this->freetext += this->payload;
|
this->freetext += (char)this->payload;
|
||||||
} else {
|
} else {
|
||||||
this->freetext =
|
this->freetext =
|
||||||
this->freetext.substring(0, this->cursor) + this->payload + this->freetext.substring(this->cursor);
|
this->freetext.substring(0, this->cursor) + (char)this->payload + this->freetext.substring(this->cursor);
|
||||||
}
|
}
|
||||||
this->cursor += 1;
|
this->cursor++;
|
||||||
uint16_t maxChars = meshtastic_Constants_DATA_PAYLOAD_LEN - (moduleConfig.canned_message.send_bell ? 1 : 0);
|
uint16_t maxChars = meshtastic_Constants_DATA_PAYLOAD_LEN - (moduleConfig.canned_message.send_bell ? 1 : 0);
|
||||||
if (this->freetext.length() > maxChars) {
|
if (this->freetext.length() > maxChars) {
|
||||||
this->cursor = maxChars;
|
this->cursor = maxChars;
|
||||||
this->freetext = this->freetext.substring(0, maxChars);
|
this->freetext = this->freetext.substring(0, maxChars);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1443,6 +1497,81 @@ void CannedMessageModule::drawDestinationSelectionScreen(OLEDDisplay *display, O
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CannedMessageModule::drawEmotePickerScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
|
{
|
||||||
|
const int headerFontHeight = FONT_HEIGHT_SMALL; // Make sure this matches your actual small font height
|
||||||
|
const int headerMargin = 2; // Extra pixels below header
|
||||||
|
const int labelGap = 6;
|
||||||
|
const int bitmapGapX = 4;
|
||||||
|
|
||||||
|
// Find max emote height (assume all same, or precalculated)
|
||||||
|
int maxEmoteHeight = 0;
|
||||||
|
for (int i = 0; i < graphics::numEmotes; ++i)
|
||||||
|
if (graphics::emotes[i].height > maxEmoteHeight)
|
||||||
|
maxEmoteHeight = graphics::emotes[i].height;
|
||||||
|
|
||||||
|
const int rowHeight = maxEmoteHeight + 2;
|
||||||
|
|
||||||
|
// Place header at top, then compute start of emote list
|
||||||
|
int headerY = y;
|
||||||
|
int listTop = headerY + headerFontHeight + headerMargin;
|
||||||
|
|
||||||
|
int visibleRows = (display->getHeight() - listTop - 2) / rowHeight;
|
||||||
|
int numEmotes = graphics::numEmotes;
|
||||||
|
|
||||||
|
// Clamp highlight index
|
||||||
|
if (emotePickerIndex < 0) emotePickerIndex = 0;
|
||||||
|
if (emotePickerIndex >= numEmotes) emotePickerIndex = numEmotes - 1;
|
||||||
|
|
||||||
|
// Determine which emote is at the top
|
||||||
|
int topIndex = emotePickerIndex - visibleRows / 2;
|
||||||
|
if (topIndex < 0) topIndex = 0;
|
||||||
|
if (topIndex > numEmotes - visibleRows) topIndex = std::max(0, numEmotes - visibleRows);
|
||||||
|
|
||||||
|
// Draw header/title
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
|
display->drawString(display->getWidth() / 2, headerY, "Select Emote");
|
||||||
|
|
||||||
|
// Draw emote rows
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
|
||||||
|
for (int vis = 0; vis < visibleRows; ++vis) {
|
||||||
|
int emoteIdx = topIndex + vis;
|
||||||
|
if (emoteIdx >= numEmotes) break;
|
||||||
|
const graphics::Emote& emote = graphics::emotes[emoteIdx];
|
||||||
|
int rowY = listTop + vis * rowHeight;
|
||||||
|
|
||||||
|
// Draw highlight box 2px taller than emote (1px margin above and below)
|
||||||
|
if (emoteIdx == emotePickerIndex) {
|
||||||
|
display->fillRect(x, rowY, display->getWidth() - 8, emote.height + 2);
|
||||||
|
display->setColor(BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emote bitmap (left), 1px margin from highlight bar top
|
||||||
|
int emoteY = rowY + 1;
|
||||||
|
display->drawXbm(x + bitmapGapX, emoteY, emote.width, emote.height, emote.bitmap);
|
||||||
|
|
||||||
|
// Emote label (right of bitmap)
|
||||||
|
display->setFont(FONT_MEDIUM);
|
||||||
|
int labelY = rowY + ((rowHeight - FONT_HEIGHT_MEDIUM) / 2);
|
||||||
|
display->drawString(x + bitmapGapX + emote.width + labelGap, labelY, emote.label);
|
||||||
|
|
||||||
|
if (emoteIdx == emotePickerIndex)
|
||||||
|
display->setColor(WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw scrollbar if needed
|
||||||
|
if (numEmotes > visibleRows) {
|
||||||
|
int scrollbarHeight = visibleRows * rowHeight;
|
||||||
|
int scrollTrackX = display->getWidth() - 6;
|
||||||
|
display->drawRect(scrollTrackX, listTop, 4, scrollbarHeight);
|
||||||
|
int scrollBarLen = std::max(6, (scrollbarHeight * visibleRows) / numEmotes);
|
||||||
|
int scrollBarPos = listTop + (scrollbarHeight * topIndex) / numEmotes;
|
||||||
|
display->fillRect(scrollTrackX, scrollBarPos, 4, scrollBarLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
this->displayHeight = display->getHeight(); // Store display height for later use
|
this->displayHeight = display->getHeight(); // Store display height for later use
|
||||||
@ -1460,6 +1589,12 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Emote Picker Screen ===
|
||||||
|
if (this->runState == CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER) {
|
||||||
|
drawEmotePickerScreen(display, state, x, y); // <-- Call your emote picker drawer here
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// === Destination Selection ===
|
// === Destination Selection ===
|
||||||
if (this->runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) {
|
if (this->runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) {
|
||||||
drawDestinationSelectionScreen(display, state, x, y);
|
drawDestinationSelectionScreen(display, state, x, y);
|
||||||
@ -1562,10 +1697,145 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
|
|||||||
display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer);
|
display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Draw Free Text input, shifted down ---
|
// --- Draw Free Text input with multi-emote support and proper line wrapping ---
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(),
|
{
|
||||||
drawWithCursor(this->freetext, this->cursor));
|
int inputY = 0 + y + FONT_HEIGHT_SMALL;
|
||||||
|
String msgWithCursor = this->drawWithCursor(this->freetext, this->cursor);
|
||||||
|
|
||||||
|
// Tokenize input into (isEmote, token) pairs
|
||||||
|
std::vector<std::pair<bool, String>> tokens;
|
||||||
|
const char* msg = msgWithCursor.c_str();
|
||||||
|
int msgLen = strlen(msg);
|
||||||
|
int pos = 0;
|
||||||
|
while (pos < msgLen) {
|
||||||
|
const graphics::Emote* foundEmote = nullptr;
|
||||||
|
int foundLen = 0;
|
||||||
|
for (int j = 0; j < graphics::numEmotes; j++) {
|
||||||
|
const char* label = graphics::emotes[j].label;
|
||||||
|
int labelLen = strlen(label);
|
||||||
|
if (labelLen == 0) continue;
|
||||||
|
if (strncmp(msg + pos, label, labelLen) == 0) {
|
||||||
|
if (!foundEmote || labelLen > foundLen) {
|
||||||
|
foundEmote = &graphics::emotes[j];
|
||||||
|
foundLen = labelLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (foundEmote) {
|
||||||
|
tokens.emplace_back(true, String(foundEmote->label));
|
||||||
|
pos += foundLen;
|
||||||
|
} else {
|
||||||
|
// Find next emote
|
||||||
|
int nextEmote = msgLen;
|
||||||
|
for (int j = 0; j < graphics::numEmotes; j++) {
|
||||||
|
const char* label = graphics::emotes[j].label;
|
||||||
|
if (!label || !*label) continue;
|
||||||
|
char* found = strstr(msg + pos, label);
|
||||||
|
if (found && (found - msg) < nextEmote) {
|
||||||
|
nextEmote = found - msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int textLen = (nextEmote > pos) ? (nextEmote - pos) : (msgLen - pos);
|
||||||
|
if (textLen > 0) {
|
||||||
|
tokens.emplace_back(false, String(msg + pos).substring(0, textLen));
|
||||||
|
pos += textLen;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Advanced word-wrapping (emotes + text, split by word, wrap by char if needed) =====
|
||||||
|
std::vector<std::vector<std::pair<bool, String>>> lines;
|
||||||
|
std::vector<std::pair<bool, String>> currentLine;
|
||||||
|
int lineWidth = 0;
|
||||||
|
int maxWidth = display->getWidth();
|
||||||
|
for (auto& token : tokens) {
|
||||||
|
if (token.first) {
|
||||||
|
// Emote
|
||||||
|
int tokenWidth = 0;
|
||||||
|
for (int j = 0; j < graphics::numEmotes; j++) {
|
||||||
|
if (token.second == graphics::emotes[j].label) {
|
||||||
|
tokenWidth = graphics::emotes[j].width + 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lineWidth + tokenWidth > maxWidth && !currentLine.empty()) {
|
||||||
|
lines.push_back(currentLine);
|
||||||
|
currentLine.clear();
|
||||||
|
lineWidth = 0;
|
||||||
|
}
|
||||||
|
currentLine.push_back(token);
|
||||||
|
lineWidth += tokenWidth;
|
||||||
|
} else {
|
||||||
|
// Text: split by words and wrap inside word if needed
|
||||||
|
String text = token.second;
|
||||||
|
int pos = 0;
|
||||||
|
while (pos < text.length()) {
|
||||||
|
// Find next space (or end)
|
||||||
|
int spacePos = text.indexOf(' ', pos);
|
||||||
|
int endPos = (spacePos == -1) ? text.length() : spacePos + 1; // Include space
|
||||||
|
String word = text.substring(pos, endPos);
|
||||||
|
int wordWidth = display->getStringWidth(word);
|
||||||
|
|
||||||
|
if (lineWidth + wordWidth > maxWidth && lineWidth > 0) {
|
||||||
|
lines.push_back(currentLine);
|
||||||
|
currentLine.clear();
|
||||||
|
lineWidth = 0;
|
||||||
|
}
|
||||||
|
// If word itself too big, split by character
|
||||||
|
if (wordWidth > maxWidth) {
|
||||||
|
int charPos = 0;
|
||||||
|
while (charPos < word.length()) {
|
||||||
|
String oneChar = word.substring(charPos, charPos + 1);
|
||||||
|
int charWidth = display->getStringWidth(oneChar);
|
||||||
|
if (lineWidth + charWidth > maxWidth && lineWidth > 0) {
|
||||||
|
lines.push_back(currentLine);
|
||||||
|
currentLine.clear();
|
||||||
|
lineWidth = 0;
|
||||||
|
}
|
||||||
|
currentLine.push_back({false, oneChar});
|
||||||
|
lineWidth += charWidth;
|
||||||
|
charPos++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentLine.push_back({false, word});
|
||||||
|
lineWidth += wordWidth;
|
||||||
|
}
|
||||||
|
pos = endPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!currentLine.empty()) lines.push_back(currentLine);
|
||||||
|
|
||||||
|
// Draw lines with emotes
|
||||||
|
int rowHeight = FONT_HEIGHT_SMALL;
|
||||||
|
int yLine = inputY;
|
||||||
|
for (auto& line : lines) {
|
||||||
|
int nextX = x;
|
||||||
|
for (auto& token : line) {
|
||||||
|
if (token.first) {
|
||||||
|
const graphics::Emote* emote = nullptr;
|
||||||
|
for (int j = 0; j < graphics::numEmotes; j++) {
|
||||||
|
if (token.second == graphics::emotes[j].label) {
|
||||||
|
emote = &graphics::emotes[j];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (emote) {
|
||||||
|
int emoteYOffset = (rowHeight - emote->height) / 2;
|
||||||
|
display->drawXbm(nextX, yLine + emoteYOffset, emote->width, emote->height, emote->bitmap);
|
||||||
|
nextX += emote->width + 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
display->drawString(nextX, yLine, token.second);
|
||||||
|
nextX += display->getStringWidth(token.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yLine += rowHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1625,9 +1895,9 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
|
|||||||
int msgLen = strlen(msg);
|
int msgLen = strlen(msg);
|
||||||
while (pos < msgLen) {
|
while (pos < msgLen) {
|
||||||
const graphics::Emote* foundEmote = nullptr;
|
const graphics::Emote* foundEmote = nullptr;
|
||||||
int foundAt = -1, foundLen = 0;
|
int foundLen = 0;
|
||||||
|
|
||||||
// Look for any emote at this pos (prefer longest match)
|
// Look for any emote label at this pos (prefer longest match)
|
||||||
for (int j = 0; j < graphics::numEmotes; j++) {
|
for (int j = 0; j < graphics::numEmotes; j++) {
|
||||||
const char* label = graphics::emotes[j].label;
|
const char* label = graphics::emotes[j].label;
|
||||||
int labelLen = strlen(label);
|
int labelLen = strlen(label);
|
||||||
@ -1635,14 +1905,11 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
|
|||||||
if (strncmp(msg + pos, label, labelLen) == 0) {
|
if (strncmp(msg + pos, label, labelLen) == 0) {
|
||||||
if (!foundEmote || labelLen > foundLen) {
|
if (!foundEmote || labelLen > foundLen) {
|
||||||
foundEmote = &graphics::emotes[j];
|
foundEmote = &graphics::emotes[j];
|
||||||
foundAt = pos;
|
|
||||||
foundLen = labelLen;
|
foundLen = labelLen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (foundEmote) {
|
||||||
if (foundEmote && foundAt == pos) {
|
|
||||||
// Emote at current pos
|
|
||||||
tokens.emplace_back(true, String(foundEmote->label));
|
tokens.emplace_back(true, String(foundEmote->label));
|
||||||
pos += foundLen;
|
pos += foundLen;
|
||||||
} else {
|
} else {
|
||||||
|
@ -18,7 +18,8 @@ enum cannedMessageModuleRunState {
|
|||||||
CANNED_MESSAGE_RUN_STATE_ACTION_DOWN,
|
CANNED_MESSAGE_RUN_STATE_ACTION_DOWN,
|
||||||
CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION,
|
CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION,
|
||||||
CANNED_MESSAGE_RUN_STATE_FREETEXT,
|
CANNED_MESSAGE_RUN_STATE_FREETEXT,
|
||||||
CANNED_MESSAGE_RUN_STATE_MESSAGE_SELECTION
|
CANNED_MESSAGE_RUN_STATE_MESSAGE_SELECTION,
|
||||||
|
CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER
|
||||||
};
|
};
|
||||||
|
|
||||||
enum CannedMessageModuleIconType { shift, backspace, space, enter };
|
enum CannedMessageModuleIconType { shift, backspace, space, enter };
|
||||||
@ -57,6 +58,9 @@ class CannedMessageModule : public SinglePortModule, public Observable<const UIF
|
|||||||
public:
|
public:
|
||||||
CannedMessageModule();
|
CannedMessageModule();
|
||||||
|
|
||||||
|
// === Emote Picker navigation ===
|
||||||
|
int emotePickerIndex = 0; // Tracks currently selected emote in the picker
|
||||||
|
|
||||||
// === Message navigation ===
|
// === Message navigation ===
|
||||||
const char *getCurrentMessage();
|
const char *getCurrentMessage();
|
||||||
const char *getPrevMessage();
|
const char *getPrevMessage();
|
||||||
@ -75,6 +79,10 @@ class CannedMessageModule : public SinglePortModule, public Observable<const UIF
|
|||||||
bool isCharInputAllowed() const;
|
bool isCharInputAllowed() const;
|
||||||
String drawWithCursor(String text, int cursor);
|
String drawWithCursor(String text, int cursor);
|
||||||
|
|
||||||
|
// === Emote Picker ===
|
||||||
|
int handleEmotePickerInput(const InputEvent *event);
|
||||||
|
void drawEmotePickerScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
// === Admin Handlers ===
|
// === Admin Handlers ===
|
||||||
void handleGetCannedMessageModuleMessages(const meshtastic_MeshPacket &req, meshtastic_AdminMessage *response);
|
void handleGetCannedMessageModuleMessages(const meshtastic_MeshPacket &req, meshtastic_AdminMessage *response);
|
||||||
void handleSetCannedMessageModuleMessages(const char *from_msg);
|
void handleSetCannedMessageModuleMessages(const char *from_msg);
|
||||||
|
Loading…
Reference in New Issue
Block a user