347 lines
12 KiB
C++
347 lines
12 KiB
C++
#include "KeyboardEntryActivity.h"
|
|
|
|
#include <I18n.h>
|
|
|
|
#include <vector>
|
|
|
|
#include "MappedInputManager.h"
|
|
#include "components/UITheme.h"
|
|
#include "fontIds.h"
|
|
|
|
void KeyboardEntryActivity::onEnter() {
|
|
Activity::onEnter();
|
|
requestUpdate();
|
|
}
|
|
|
|
void KeyboardEntryActivity::onExit() { Activity::onExit(); }
|
|
|
|
int KeyboardEntryActivity::getContentRowCount() const { return ABC_ROWS; }
|
|
|
|
int KeyboardEntryActivity::getTotalRowCount() const { return getContentRowCount() + 1; }
|
|
|
|
int KeyboardEntryActivity::getBottomKeyCount() const { return isPassword ? 6 : 5; }
|
|
|
|
bool KeyboardEntryActivity::isBottomRow(const int row) const { return row == getContentRowCount(); }
|
|
|
|
const char* KeyboardEntryActivity::getShiftLabel() const { return tr(shiftState > 0 ? STR_SHIFT_CAPS : STR_SHIFT); }
|
|
|
|
char KeyboardEntryActivity::getSelectedChar() const {
|
|
const KeyDef(*layout)[COLS] = symMode ? symLayout : abcLayout;
|
|
|
|
if (selectedRow < 0 || selectedRow >= getContentRowCount()) return '\0';
|
|
if (selectedCol < 0 || selectedCol >= COLS) return '\0';
|
|
|
|
const KeyDef& key = layout[selectedRow][selectedCol];
|
|
return (shiftState > 0 && key.secondary != '\0') ? key.secondary : key.primary;
|
|
}
|
|
|
|
char KeyboardEntryActivity::getAlternativeChar() const {
|
|
if (symMode) return '\0';
|
|
|
|
const KeyDef(*layout)[COLS] = abcLayout;
|
|
|
|
if (selectedRow < 0 || selectedRow >= getContentRowCount()) return '\0';
|
|
if (selectedCol < 0 || selectedCol >= COLS) return '\0';
|
|
|
|
const KeyDef& key = layout[selectedRow][selectedCol];
|
|
const char current = getSelectedChar();
|
|
if (current == key.primary && key.secondary != '\0') return key.secondary;
|
|
if (current == key.secondary) return key.primary;
|
|
return '\0';
|
|
}
|
|
|
|
bool KeyboardEntryActivity::insertChar(char c) {
|
|
if (c == '\0') return true;
|
|
if (maxLength != 0 && text.length() >= maxLength) return true;
|
|
|
|
text += c;
|
|
return true;
|
|
}
|
|
|
|
SpecialKeyType KeyboardEntryActivity::getBottomSpecialKey(int index) const {
|
|
if (isPassword) {
|
|
switch (index) {
|
|
case 0:
|
|
return SpecShift;
|
|
case 1:
|
|
return SpecMode;
|
|
case 2:
|
|
return SpecReveal;
|
|
case 3:
|
|
return SpecSpace;
|
|
case 4:
|
|
return SpecDel;
|
|
case 5:
|
|
default:
|
|
return SpecOk;
|
|
}
|
|
}
|
|
|
|
switch (index) {
|
|
case 0:
|
|
return SpecShift;
|
|
case 1:
|
|
return SpecMode;
|
|
case 2:
|
|
return SpecSpace;
|
|
case 3:
|
|
return SpecDel;
|
|
case 4:
|
|
default:
|
|
return SpecOk;
|
|
}
|
|
}
|
|
|
|
bool KeyboardEntryActivity::handleKeyPress() {
|
|
if (isBottomRow(selectedRow)) {
|
|
switch (getBottomSpecialKey(selectedCol)) {
|
|
case SpecShift:
|
|
if (symMode) return true;
|
|
shiftState = (shiftState + 1) % 2;
|
|
return true;
|
|
case SpecMode: {
|
|
symMode = !symMode;
|
|
int maxRow = getTotalRowCount() - 1;
|
|
if (selectedRow > maxRow) selectedRow = maxRow;
|
|
if (isBottomRow(selectedRow)) {
|
|
int bottomCount = getBottomKeyCount();
|
|
if (selectedCol >= bottomCount) selectedCol = bottomCount - 1;
|
|
} else {
|
|
if (selectedCol >= COLS) selectedCol = COLS - 1;
|
|
}
|
|
return true;
|
|
}
|
|
case SpecReveal:
|
|
passwordVisible = !passwordVisible;
|
|
return true;
|
|
case SpecSpace:
|
|
return insertChar(' ');
|
|
case SpecDel:
|
|
if (!text.empty()) {
|
|
text.pop_back();
|
|
}
|
|
return true;
|
|
case SpecOk:
|
|
onComplete(text);
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return insertChar(getSelectedChar());
|
|
}
|
|
|
|
void KeyboardEntryActivity::loop() {
|
|
const int totalRows = getTotalRowCount();
|
|
|
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Up}, [this, totalRows] {
|
|
bool wasBottom = isBottomRow(selectedRow);
|
|
selectedRow = ButtonNavigator::previousIndex(selectedRow, totalRows);
|
|
if (wasBottom && !isBottomRow(selectedRow)) {
|
|
selectedCol = selectedCol * COLS / getBottomKeyCount();
|
|
} else if (!wasBottom && isBottomRow(selectedRow)) {
|
|
selectedCol = selectedCol * getBottomKeyCount() / COLS;
|
|
}
|
|
int maxCol = isBottomRow(selectedRow) ? getBottomKeyCount() - 1 : COLS - 1;
|
|
if (selectedCol > maxCol) selectedCol = maxCol;
|
|
requestUpdate();
|
|
});
|
|
|
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Down}, [this, totalRows] {
|
|
bool wasBottom = isBottomRow(selectedRow);
|
|
selectedRow = ButtonNavigator::nextIndex(selectedRow, totalRows);
|
|
if (wasBottom && !isBottomRow(selectedRow)) {
|
|
selectedCol = selectedCol * COLS / getBottomKeyCount();
|
|
} else if (!wasBottom && isBottomRow(selectedRow)) {
|
|
selectedCol = selectedCol * getBottomKeyCount() / COLS;
|
|
}
|
|
int maxCol = isBottomRow(selectedRow) ? getBottomKeyCount() - 1 : COLS - 1;
|
|
if (selectedCol > maxCol) selectedCol = maxCol;
|
|
requestUpdate();
|
|
});
|
|
|
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Left}, [this] {
|
|
int maxCol = isBottomRow(selectedRow) ? getBottomKeyCount() - 1 : COLS - 1;
|
|
selectedCol = ButtonNavigator::previousIndex(selectedCol, maxCol + 1);
|
|
requestUpdate();
|
|
});
|
|
|
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Right}, [this] {
|
|
int maxCol = isBottomRow(selectedRow) ? getBottomKeyCount() - 1 : COLS - 1;
|
|
selectedCol = ButtonNavigator::nextIndex(selectedCol, maxCol + 1);
|
|
requestUpdate();
|
|
});
|
|
|
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
|
confirmHeld = true;
|
|
confirmLongHandled = false;
|
|
}
|
|
|
|
if (confirmHeld && !confirmLongHandled && mappedInput.isPressed(MappedInputManager::Button::Confirm) &&
|
|
mappedInput.getHeldTime() > LONG_PRESS_MS) {
|
|
char alt = getAlternativeChar();
|
|
if (alt != '\0') {
|
|
insertChar(alt);
|
|
requestUpdate();
|
|
confirmLongHandled = true;
|
|
}
|
|
}
|
|
|
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
|
if (confirmHeld && !confirmLongHandled) {
|
|
if (handleKeyPress()) {
|
|
requestUpdate();
|
|
}
|
|
}
|
|
confirmHeld = false;
|
|
confirmLongHandled = false;
|
|
}
|
|
|
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
|
onCancel();
|
|
}
|
|
}
|
|
|
|
void KeyboardEntryActivity::render(RenderLock&&) {
|
|
renderer.clearScreen();
|
|
|
|
const auto pageWidth = renderer.getScreenWidth();
|
|
const auto pageHeight = renderer.getScreenHeight();
|
|
const auto& metrics = UITheme::getInstance().getMetrics();
|
|
const Rect contentRect = UITheme::getContentRect(renderer, true, true);
|
|
|
|
GUI.drawHeader(renderer, Rect{contentRect.x, metrics.topPadding, contentRect.width, metrics.headerHeight},
|
|
title.c_str());
|
|
|
|
const int lineHeight = renderer.getLineHeight(UI_12_FONT_ID);
|
|
const int inputStartY = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing +
|
|
metrics.verticalSpacing * 4 + metrics.keyboardVerticalOffset;
|
|
int inputHeight = 0;
|
|
|
|
std::string displayText;
|
|
if (isPassword && !passwordVisible) {
|
|
displayText = std::string(text.length(), '*');
|
|
} else {
|
|
displayText = text;
|
|
}
|
|
|
|
displayText += "_";
|
|
|
|
int lineStartIdx = 0;
|
|
int lineEndIdx = displayText.length();
|
|
int textWidth = 0;
|
|
while (true) {
|
|
std::string lineText = displayText.substr(lineStartIdx, lineEndIdx - lineStartIdx);
|
|
textWidth = renderer.getTextWidth(UI_12_FONT_ID, lineText.c_str());
|
|
if (textWidth <= contentRect.width - 2 * metrics.contentSidePadding) {
|
|
if (metrics.keyboardCenteredText) {
|
|
const int centeredX = contentRect.x + (contentRect.width - textWidth) / 2;
|
|
renderer.drawText(UI_12_FONT_ID, centeredX, inputStartY + inputHeight, lineText.c_str());
|
|
} else {
|
|
renderer.drawText(UI_12_FONT_ID, contentRect.x + metrics.contentSidePadding, inputStartY + inputHeight,
|
|
lineText.c_str());
|
|
}
|
|
if (lineEndIdx == displayText.length()) {
|
|
break;
|
|
}
|
|
|
|
inputHeight += lineHeight;
|
|
lineStartIdx = lineEndIdx;
|
|
lineEndIdx = displayText.length();
|
|
} else {
|
|
lineEndIdx -= 1;
|
|
}
|
|
}
|
|
|
|
GUI.drawTextField(renderer, Rect{contentRect.x, inputStartY, contentRect.width, inputHeight}, textWidth);
|
|
|
|
const int keyHeight = metrics.keyboardKeyHeight;
|
|
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 keyboardStartY = metrics.keyboardBottomAligned
|
|
? pageHeight - metrics.buttonHintsHeight - metrics.verticalSpacing -
|
|
(keyHeight + keySpacing) * getTotalRowCount() + metrics.keyboardVerticalOffset
|
|
: inputStartY + inputHeight + lineHeight + metrics.verticalSpacing;
|
|
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);
|
|
|
|
for (int col = 0; col < COLS; col++) {
|
|
const KeyDef& key = layout[row][col];
|
|
const int keyX = leftMargin + 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';
|
|
GUI.drawKeyboardKey(renderer, Rect{keyX, rowY, keyWidth, keyHeight}, primaryBuf, isSelected,
|
|
showSecondary ? secondaryBuf : nullptr);
|
|
}
|
|
}
|
|
|
|
const int bottomRowGap = metrics.keyboardBottomKeySpacing > 0 ? 4 : 0;
|
|
const int bottomRowY = keyboardStartY + contentRows * (keyHeight + keySpacing) + bottomRowGap;
|
|
const int bkSpacing = metrics.keyboardBottomKeySpacing;
|
|
const int contentTotalWidth = COLS * keyWidth + (COLS - 1) * keySpacing;
|
|
const int bottomKeyCount = getBottomKeyCount();
|
|
const int bottomKeyWidth = (contentTotalWidth - (bottomKeyCount - 1) * bkSpacing) / bottomKeyCount;
|
|
const bool bottomSelected = isBottomRow(selectedRow);
|
|
|
|
struct BottomKeyInfo {
|
|
KeyboardKeyType themeType;
|
|
const char* label;
|
|
};
|
|
|
|
std::vector<BottomKeyInfo> bottomKeys;
|
|
bottomKeys.reserve(6);
|
|
bottomKeys.push_back({KeyboardKeyType::Shift, getShiftLabel()});
|
|
bottomKeys.push_back({KeyboardKeyType::Mode, symMode ? tr(STR_ABC) : "#@!"});
|
|
if (isPassword) {
|
|
bottomKeys.push_back({KeyboardKeyType::Reveal, passwordVisible ? tr(STR_HIDE) : tr(STR_SHOW)});
|
|
}
|
|
bottomKeys.push_back({KeyboardKeyType::Space, nullptr});
|
|
bottomKeys.push_back({KeyboardKeyType::Del, nullptr});
|
|
bottomKeys.push_back({KeyboardKeyType::Ok, tr(STR_OK_BUTTON)});
|
|
|
|
for (int i = 0; i < bottomKeyCount; i++) {
|
|
const int keyX = leftMargin + i * (bottomKeyWidth + bkSpacing);
|
|
const bool isSelected = bottomSelected && i == selectedCol;
|
|
|
|
GUI.drawKeyboardKey(renderer, Rect{keyX, bottomRowY, bottomKeyWidth, keyHeight}, bottomKeys[i].label, isSelected,
|
|
nullptr, bottomKeys[i].themeType);
|
|
}
|
|
|
|
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_LEFT), tr(STR_DIR_RIGHT));
|
|
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
|
|
|
GUI.drawSideButtonHints(renderer, ">", "<");
|
|
|
|
renderer.displayBuffer();
|
|
}
|
|
|
|
void KeyboardEntryActivity::onComplete(std::string text) {
|
|
setResult(KeyboardResult{std::move(text)});
|
|
finish();
|
|
}
|
|
|
|
void KeyboardEntryActivity::onCancel() {
|
|
ActivityResult result;
|
|
result.isCancelled = true;
|
|
setResult(std::move(result));
|
|
finish();
|
|
}
|