feat(keyboard): add URL mode with 3x3 snippet grid aligned to bottom row

This commit is contained in:
pablohc
2026-04-15 11:58:21 +02:00
committed by jpirnay
parent 0491587df8
commit 155650d23c
2 changed files with 143 additions and 39 deletions
+136 -39
View File
@@ -16,7 +16,15 @@ void KeyboardEntryActivity::onEnter() {
void KeyboardEntryActivity::onExit() { Activity::onExit(); }
int KeyboardEntryActivity::getContentRowCount() const { return ABC_ROWS; }
int KeyboardEntryActivity::getContentRowCount() const {
if (urlMode) return 3;
return symMode ? SYM_ROWS : ABC_ROWS;
}
int KeyboardEntryActivity::getContentColCount() const {
if (urlMode) return 3;
return COLS;
}
int KeyboardEntryActivity::getTotalRowCount() const { return getContentRowCount() + 1; }
@@ -56,26 +64,50 @@ bool KeyboardEntryActivity::insertChar(char c) {
return true;
}
void KeyboardEntryActivity::insertString(const std::string& str) {
if (maxLength != 0 && text.length() + str.length() > maxLength) return;
text.insert(cursorPos, str);
cursorPos += str.length();
}
bool KeyboardEntryActivity::handleKeyPress() {
if (isBottomRow(selectedRow)) {
switch (static_cast<SpecialKeyType>(selectedCol)) {
case SpecShift:
if (urlMode) return true;
if (symMode) return true;
shiftState = (shiftState + 1) % 2;
return true;
case SpecMode: {
if (urlMode) {
urlMode = false;
symMode = false;
requestUpdate();
return true;
}
symMode = !symMode;
int maxRow = getTotalRowCount() - 1;
if (selectedRow > maxRow) selectedRow = maxRow;
if (isBottomRow(selectedRow)) {
if (selectedCol >= BOTTOM_KEY_COUNT) selectedCol = BOTTOM_KEY_COUNT - 1;
} else {
if (selectedCol >= COLS) selectedCol = COLS - 1;
if (selectedCol >= getContentColCount()) selectedCol = getContentColCount() - 1;
}
return true;
}
case SpecSpace:
return insertChar(' ');
if (inputType == InputType::Url) {
urlMode = !urlMode;
if (urlMode) {
symMode = false;
}
selectedRow = getTotalRowCount() - 1;
selectedCol = SpecSpace;
requestUpdate();
} else {
return insertChar(' ');
}
return true;
case SpecDel:
if (cursorPos > 0 && !text.empty()) {
text.erase(cursorPos - 1, 1);
@@ -90,6 +122,14 @@ bool KeyboardEntryActivity::handleKeyPress() {
}
}
if (urlMode) {
const int idx = selectedCol + selectedRow * 3;
if (idx < URL_SNIPPET_COUNT) {
insertString(urlSnippets[idx]);
}
return true;
}
return insertChar(getSelectedChar());
}
@@ -111,13 +151,25 @@ void KeyboardEntryActivity::loop() {
if (mappedInput.wasReleased(MappedInputManager::Button::Up)) {
if (upHeld && !upLongHandled && !cursorMode) {
bool wasBottom = isBottomRow(selectedRow);
const int contentCols = getContentColCount();
selectedRow = ButtonNavigator::previousIndex(selectedRow, totalRows);
if (wasBottom && !isBottomRow(selectedRow)) {
selectedCol = selectedCol * 2;
if (urlMode) {
selectedCol = selectedCol - 1;
if (selectedCol < 0) selectedCol = 0;
if (selectedCol >= 3) selectedCol = 2;
} else {
selectedCol = selectedCol * 2;
}
} else if (!wasBottom && isBottomRow(selectedRow)) {
selectedCol = selectedCol / 2;
if (urlMode) {
selectedCol = selectedCol + 1;
if (selectedCol > BOTTOM_KEY_COUNT - 1) selectedCol = BOTTOM_KEY_COUNT - 1;
} else {
selectedCol = selectedCol / 2;
}
}
int maxCol = isBottomRow(selectedRow) ? BOTTOM_KEY_COUNT - 1 : COLS - 1;
int maxCol = isBottomRow(selectedRow) ? BOTTOM_KEY_COUNT - 1 : contentCols - 1;
if (selectedCol > maxCol) selectedCol = maxCol;
requestUpdate();
}
@@ -143,13 +195,25 @@ void KeyboardEntryActivity::loop() {
if (mappedInput.wasReleased(MappedInputManager::Button::Down)) {
if (downHeld && !downLongHandled && !cursorMode) {
bool wasBottom = isBottomRow(selectedRow);
const int contentCols = getContentColCount();
selectedRow = ButtonNavigator::nextIndex(selectedRow, totalRows);
if (wasBottom && !isBottomRow(selectedRow)) {
selectedCol = selectedCol * 2;
if (urlMode) {
selectedCol = selectedCol - 1;
if (selectedCol < 0) selectedCol = 0;
if (selectedCol >= 3) selectedCol = 2;
} else {
selectedCol = selectedCol * 2;
}
} else if (!wasBottom && isBottomRow(selectedRow)) {
selectedCol = selectedCol / 2;
if (urlMode) {
selectedCol = selectedCol + 1;
if (selectedCol > BOTTOM_KEY_COUNT - 1) selectedCol = BOTTOM_KEY_COUNT - 1;
} else {
selectedCol = selectedCol / 2;
}
}
int maxCol = isBottomRow(selectedRow) ? BOTTOM_KEY_COUNT - 1 : COLS - 1;
int maxCol = isBottomRow(selectedRow) ? BOTTOM_KEY_COUNT - 1 : contentCols - 1;
if (selectedCol > maxCol) selectedCol = maxCol;
requestUpdate();
}
@@ -165,7 +229,7 @@ void KeyboardEntryActivity::loop() {
}
return;
}
int maxCol = isBottomRow(selectedRow) ? BOTTOM_KEY_COUNT - 1 : COLS - 1;
int maxCol = isBottomRow(selectedRow) ? BOTTOM_KEY_COUNT - 1 : getContentColCount() - 1;
selectedCol = ButtonNavigator::previousIndex(selectedCol, maxCol + 1);
requestUpdate();
});
@@ -181,7 +245,7 @@ void KeyboardEntryActivity::loop() {
}
return;
}
int maxCol = isBottomRow(selectedRow) ? BOTTOM_KEY_COUNT - 1 : COLS - 1;
int maxCol = isBottomRow(selectedRow) ? BOTTOM_KEY_COUNT - 1 : getContentColCount() - 1;
selectedCol = ButtonNavigator::nextIndex(selectedCol, maxCol + 1);
requestUpdate();
});
@@ -384,8 +448,9 @@ void KeyboardEntryActivity::render(RenderLock&&) {
const int keyHeight = metrics.keyboardKeyHeight;
const int bottomKeyHeight = metrics.keyboardBottomKeyHeight;
const int keySpacing = metrics.keyboardKeySpacing;
const int keyWidth = (pageWidth * 95 / 100 - (COLS - 1) * keySpacing) / COLS;
const int leftMargin = (pageWidth - (COLS * keyWidth + (COLS - 1) * keySpacing)) / 2;
const int contentCols = getContentColCount();
const int keyWidth = (pageWidth * 95 / 100 - (contentCols - 1) * keySpacing) / contentCols;
const int leftMargin = (pageWidth - (contentCols * keyWidth + (contentCols - 1) * keySpacing)) / 2;
const int bottomRowGap = metrics.keyboardBottomKeySpacing > 0 ? 4 : 0;
const int keyboardStartY = metrics.keyboardBottomAligned
@@ -394,38 +459,62 @@ void KeyboardEntryActivity::render(RenderLock&&) {
bottomRowGap + metrics.keyboardVerticalOffset
: inputStartY + inputHeight + lineHeight + metrics.verticalSpacing;
const int bkSpacing = metrics.keyboardBottomKeySpacing;
const int contentTotalWidth =
COLS * ((pageWidth * 95 / 100 - (COLS - 1) * keySpacing) / COLS) + (COLS - 1) * keySpacing;
const int bottomKeyWidth = (contentTotalWidth - (BOTTOM_KEY_COUNT - 1) * bkSpacing) / BOTTOM_KEY_COUNT;
const int bottomLeftMargin =
(pageWidth - (BOTTOM_KEY_COUNT * bottomKeyWidth + (BOTTOM_KEY_COUNT - 1) * bkSpacing)) / 2;
int urlLeftMargin = leftMargin;
int urlKeyWidth = keyWidth;
if (urlMode) {
const int urlCols = 3;
urlKeyWidth = keyWidth;
const int urlTotalWidth = urlCols * urlKeyWidth + (urlCols - 1) * keySpacing;
const int urlCenterX = bottomLeftMargin + SpecSpace * (bottomKeyWidth + bkSpacing) + bottomKeyWidth / 2;
urlLeftMargin = urlCenterX - urlTotalWidth / 2;
}
const KeyDef(*layout)[COLS] = symMode ? symLayout : abcLayout;
const int contentRows = getContentRowCount();
for (int row = 0; row < contentRows; row++) {
const int rowY = keyboardStartY + row * (keyHeight + keySpacing);
const int rowLeftMargin = urlMode ? urlLeftMargin : leftMargin;
for (int col = 0; col < COLS; col++) {
const KeyDef& key = layout[row][col];
const int keyX = leftMargin + col * (keyWidth + keySpacing);
for (int col = 0; col < contentCols; col++) {
const int keyX = rowLeftMargin + col * (keyWidth + keySpacing);
const bool isSelected = row == selectedRow && col == selectedCol;
char primaryChar = key.primary;
char secondaryChar = key.secondary;
if (!symMode && shiftState > 0 && key.secondary != '\0') {
primaryChar = key.secondary;
secondaryChar = key.primary;
}
const char primaryBuf[2] = {primaryChar, '\0'};
const char secondaryBuf[2] = {secondaryChar, '\0'};
const bool showSecondary = !symMode && row == 0 && secondaryChar != '\0';
const bool activeKeySelected = isSelected && !cursorMode;
GUI.drawKeyboardKey(renderer, Rect{keyX, rowY, keyWidth, keyHeight}, primaryBuf, activeKeySelected,
showSecondary ? secondaryBuf : nullptr);
if (urlMode) {
const int snippetIdx = col + row * 3;
if (snippetIdx < URL_SNIPPET_COUNT) {
GUI.drawKeyboardKey(renderer, Rect{keyX, rowY, keyWidth, keyHeight}, urlSnippets[snippetIdx],
activeKeySelected, nullptr);
}
} else {
const KeyDef& key = layout[row][col];
char primaryChar = key.primary;
char secondaryChar = key.secondary;
if (!symMode && shiftState > 0 && key.secondary != '\0') {
primaryChar = key.secondary;
secondaryChar = key.primary;
}
const char primaryBuf[2] = {primaryChar, '\0'};
const char secondaryBuf[2] = {secondaryChar, '\0'};
const bool showSecondary = !symMode && row == 0 && secondaryChar != '\0';
GUI.drawKeyboardKey(renderer, Rect{keyX, rowY, keyWidth, keyHeight}, primaryBuf, activeKeySelected,
showSecondary ? secondaryBuf : nullptr);
}
}
}
const int bottomRowY = keyboardStartY + contentRows * (keyHeight + keySpacing) + bottomRowGap;
const int bkSpacing = metrics.keyboardBottomKeySpacing;
const int contentTotalWidth = COLS * keyWidth + (COLS - 1) * keySpacing;
const int bottomKeyWidth = (contentTotalWidth - (BOTTOM_KEY_COUNT - 1) * bkSpacing) / BOTTOM_KEY_COUNT;
const bool bottomSelected = isBottomRow(selectedRow);
struct BottomKeyInfo {
@@ -433,15 +522,16 @@ void KeyboardEntryActivity::render(RenderLock&&) {
const char* label;
};
const BottomKeyInfo bottomKeys[BOTTOM_KEY_COUNT] = {
{KeyboardKeyType::Shift, symMode ? shiftString[0] : shiftString[shiftState]},
{KeyboardKeyType::Mode, symMode ? "abc" : "#@!"},
{KeyboardKeyType::Space, nullptr},
{KeyboardKeyType::Shift, (symMode || urlMode) ? shiftString[0] : shiftString[shiftState]},
{KeyboardKeyType::Mode, urlMode ? "abc" : (symMode ? "abc" : "#@!")},
{inputType == InputType::Url ? KeyboardKeyType::Mode : KeyboardKeyType::Space,
inputType == InputType::Url ? "URL" : nullptr},
{KeyboardKeyType::Del, nullptr},
{KeyboardKeyType::Ok, tr(STR_OK_BUTTON)},
};
for (int i = 0; i < BOTTOM_KEY_COUNT; i++) {
const int keyX = leftMargin + i * (bottomKeyWidth + bkSpacing);
const int keyX = bottomLeftMargin + i * (bottomKeyWidth + bkSpacing);
const bool isSelected = bottomSelected && i == selectedCol;
const bool activeKeySelected = isSelected && !cursorMode;
@@ -452,12 +542,13 @@ void KeyboardEntryActivity::render(RenderLock&&) {
if (cursorMode) {
int selKeyX, selKeyY, selKeyW, selKeyH;
if (isBottomRow(selectedRow)) {
selKeyX = leftMargin + selectedCol * (bottomKeyWidth + bkSpacing);
selKeyX = bottomLeftMargin + selectedCol * (bottomKeyWidth + bkSpacing);
selKeyY = bottomRowY;
selKeyW = bottomKeyWidth;
selKeyH = bottomKeyHeight;
} else {
selKeyX = leftMargin + selectedCol * (keyWidth + keySpacing);
const int rowLM = urlMode ? urlLeftMargin : leftMargin;
selKeyX = rowLM + selectedCol * (keyWidth + keySpacing);
selKeyY = keyboardStartY + selectedRow * (keyHeight + keySpacing);
selKeyW = keyWidth;
selKeyH = keyHeight;
@@ -465,6 +556,12 @@ void KeyboardEntryActivity::render(RenderLock&&) {
if (isBottomRow(selectedRow)) {
GUI.drawKeyboardKey(renderer, Rect{selKeyX, selKeyY, selKeyW, selKeyH}, bottomKeys[selectedCol].label, true,
nullptr, bottomKeys[selectedCol].themeType, true);
} else if (urlMode) {
const int idx = selectedCol + selectedRow * 3;
if (idx < URL_SNIPPET_COUNT) {
GUI.drawKeyboardKey(renderer, Rect{selKeyX, selKeyY, selKeyW, selKeyH}, urlSnippets[idx], true, nullptr,
KeyboardKeyType::Normal, true);
}
} else {
const KeyDef& selKey = layout[selectedRow][selectedCol];
char selPrimary = selKey.primary;
@@ -57,6 +57,11 @@ class KeyboardEntryActivity : public Activity {
bool downHeld = false;
bool downLongHandled = false;
bool urlMode = false;
static constexpr int URL_SNIPPET_COUNT = 9;
static constexpr const char* const urlSnippets[URL_SNIPPET_COUNT] = {
"https://", "www.", ".com", "http://", "192.168.", ".org", "/opds", ":8080", ".net"};
void onComplete(std::string text);
void onCancel();
@@ -157,10 +162,12 @@ class KeyboardEntryActivity : public Activity {
static const char* const shiftString[2];
int getContentRowCount() const;
int getContentColCount() const;
int getTotalRowCount() const;
bool isBottomRow(int row) const;
char getSelectedChar() const;
char getAlternativeChar() const;
bool handleKeyPress();
bool insertChar(char c);
void insertString(const std::string& str);
};