feat(keyboard): add cursor mode for text navigation and editing
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
void KeyboardEntryActivity::onEnter() {
|
void KeyboardEntryActivity::onEnter() {
|
||||||
Activity::onEnter();
|
Activity::onEnter();
|
||||||
|
cursorPos = text.length();
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +59,8 @@ bool KeyboardEntryActivity::insertChar(char c) {
|
|||||||
if (c == '\0') return true;
|
if (c == '\0') return true;
|
||||||
if (maxLength != 0 && text.length() >= maxLength) return true;
|
if (maxLength != 0 && text.length() >= maxLength) return true;
|
||||||
|
|
||||||
text += c;
|
text.insert(cursorPos, 1, c);
|
||||||
|
cursorPos++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,8 +123,9 @@ bool KeyboardEntryActivity::handleKeyPress() {
|
|||||||
case SpecSpace:
|
case SpecSpace:
|
||||||
return insertChar(' ');
|
return insertChar(' ');
|
||||||
case SpecDel:
|
case SpecDel:
|
||||||
if (!text.empty()) {
|
if (cursorPos > 0 && !text.empty()) {
|
||||||
text.pop_back();
|
text.erase(cursorPos - 1, 1);
|
||||||
|
cursorPos--;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case SpecOk:
|
case SpecOk:
|
||||||
@@ -139,39 +142,84 @@ bool KeyboardEntryActivity::handleKeyPress() {
|
|||||||
void KeyboardEntryActivity::loop() {
|
void KeyboardEntryActivity::loop() {
|
||||||
const int totalRows = getTotalRowCount();
|
const int totalRows = getTotalRowCount();
|
||||||
|
|
||||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Up}, [this, totalRows] {
|
if (!cursorMode && mappedInput.wasPressed(MappedInputManager::Button::Up)) {
|
||||||
bool wasBottom = isBottomRow(selectedRow);
|
upHeld = true;
|
||||||
selectedRow = ButtonNavigator::previousIndex(selectedRow, totalRows);
|
upLongHandled = false;
|
||||||
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] {
|
if (upHeld && !upLongHandled && mappedInput.isPressed(MappedInputManager::Button::Up) &&
|
||||||
bool wasBottom = isBottomRow(selectedRow);
|
mappedInput.getHeldTime() > LONG_PRESS_MS) {
|
||||||
selectedRow = ButtonNavigator::nextIndex(selectedRow, totalRows);
|
cursorMode = true;
|
||||||
if (wasBottom && !isBottomRow(selectedRow)) {
|
upLongHandled = true;
|
||||||
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();
|
requestUpdate();
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if (mappedInput.wasReleased(MappedInputManager::Button::Up)) {
|
||||||
|
if (upHeld && !upLongHandled && !cursorMode) {
|
||||||
|
bool wasBottom = isBottomRow(selectedRow);
|
||||||
|
selectedRow = ButtonNavigator::previousIndex(selectedRow, totalRows);
|
||||||
|
if (wasBottom && !isBottomRow(selectedRow)) {
|
||||||
|
selectedCol = selectedCol * 2;
|
||||||
|
} else if (!wasBottom && isBottomRow(selectedRow)) {
|
||||||
|
selectedCol = selectedCol / 2;
|
||||||
|
}
|
||||||
|
int maxCol = isBottomRow(selectedRow) ? getBottomKeyCount() - 1 : COLS - 1;
|
||||||
|
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
upHeld = false;
|
||||||
|
upLongHandled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappedInput.wasPressed(MappedInputManager::Button::Down)) {
|
||||||
|
downHeld = true;
|
||||||
|
if (cursorMode) {
|
||||||
|
cursorMode = false;
|
||||||
|
downLongHandled = true;
|
||||||
|
requestUpdate();
|
||||||
|
} else {
|
||||||
|
downLongHandled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappedInput.wasReleased(MappedInputManager::Button::Down)) {
|
||||||
|
if (downHeld && !downLongHandled && !cursorMode) {
|
||||||
|
bool wasBottom = isBottomRow(selectedRow);
|
||||||
|
selectedRow = ButtonNavigator::nextIndex(selectedRow, totalRows);
|
||||||
|
if (wasBottom && !isBottomRow(selectedRow)) {
|
||||||
|
selectedCol = selectedCol * 2;
|
||||||
|
} else if (!wasBottom && isBottomRow(selectedRow)) {
|
||||||
|
selectedCol = selectedCol / 2;
|
||||||
|
}
|
||||||
|
int maxCol = isBottomRow(selectedRow) ? getBottomKeyCount() - 1 : COLS - 1;
|
||||||
|
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
downHeld = false;
|
||||||
|
downLongHandled = false;
|
||||||
|
}
|
||||||
|
|
||||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Left}, [this] {
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Left}, [this] {
|
||||||
|
if (cursorMode) {
|
||||||
|
if (cursorPos > 0) {
|
||||||
|
cursorPos--;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
int maxCol = isBottomRow(selectedRow) ? getBottomKeyCount() - 1 : COLS - 1;
|
int maxCol = isBottomRow(selectedRow) ? getBottomKeyCount() - 1 : COLS - 1;
|
||||||
selectedCol = ButtonNavigator::previousIndex(selectedCol, maxCol + 1);
|
selectedCol = ButtonNavigator::previousIndex(selectedCol, maxCol + 1);
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Right}, [this] {
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Right}, [this] {
|
||||||
|
if (cursorMode) {
|
||||||
|
if (cursorPos < text.length()) {
|
||||||
|
cursorPos++;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
int maxCol = isBottomRow(selectedRow) ? getBottomKeyCount() - 1 : COLS - 1;
|
int maxCol = isBottomRow(selectedRow) ? getBottomKeyCount() - 1 : COLS - 1;
|
||||||
selectedCol = ButtonNavigator::nextIndex(selectedCol, maxCol + 1);
|
selectedCol = ButtonNavigator::nextIndex(selectedCol, maxCol + 1);
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
@@ -230,15 +278,29 @@ void KeyboardEntryActivity::render(RenderLock&&) {
|
|||||||
displayText = text;
|
displayText = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayText += "_";
|
|
||||||
|
|
||||||
int lineStartIdx = 0;
|
int lineStartIdx = 0;
|
||||||
int lineEndIdx = displayText.length();
|
int lineEndIdx = displayText.length();
|
||||||
int textWidth = 0;
|
int textWidth = 0;
|
||||||
|
int cursorPixelX = metrics.contentSidePadding;
|
||||||
|
int cursorLineY = inputStartY;
|
||||||
|
bool cursorDrawn = false;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
std::string lineText = displayText.substr(lineStartIdx, lineEndIdx - lineStartIdx);
|
std::string lineText = displayText.substr(lineStartIdx, lineEndIdx - lineStartIdx);
|
||||||
textWidth = renderer.getTextWidth(UI_12_FONT_ID, lineText.c_str());
|
textWidth = renderer.getTextWidth(UI_12_FONT_ID, lineText.c_str());
|
||||||
if (textWidth <= contentRect.width - 2 * metrics.contentSidePadding) {
|
if (textWidth <= pageWidth - 2 * metrics.contentSidePadding) {
|
||||||
|
if (!cursorDrawn && cursorPos >= lineStartIdx && cursorPos <= lineEndIdx) {
|
||||||
|
std::string beforeCursor = displayText.substr(lineStartIdx, cursorPos - lineStartIdx);
|
||||||
|
int beforeWidth = renderer.getTextWidth(UI_12_FONT_ID, beforeCursor.c_str());
|
||||||
|
if (metrics.keyboardCenteredText) {
|
||||||
|
cursorPixelX = (pageWidth - textWidth) / 2 + beforeWidth;
|
||||||
|
} else {
|
||||||
|
cursorPixelX = metrics.contentSidePadding + beforeWidth;
|
||||||
|
}
|
||||||
|
cursorLineY = inputStartY + inputHeight;
|
||||||
|
cursorDrawn = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (metrics.keyboardCenteredText) {
|
if (metrics.keyboardCenteredText) {
|
||||||
const int centeredX = contentRect.x + (contentRect.width - textWidth) / 2;
|
const int centeredX = contentRect.x + (contentRect.width - textWidth) / 2;
|
||||||
renderer.drawText(UI_12_FONT_ID, centeredX, inputStartY + inputHeight, lineText.c_str());
|
renderer.drawText(UI_12_FONT_ID, centeredX, inputStartY + inputHeight, lineText.c_str());
|
||||||
@@ -260,6 +322,18 @@ void KeyboardEntryActivity::render(RenderLock&&) {
|
|||||||
|
|
||||||
GUI.drawTextField(renderer, Rect{contentRect.x, inputStartY, contentRect.width, inputHeight}, textWidth);
|
GUI.drawTextField(renderer, Rect{contentRect.x, inputStartY, contentRect.width, inputHeight}, textWidth);
|
||||||
|
|
||||||
|
const int cursorCharWidth = renderer.getTextWidth(UI_12_FONT_ID, "_");
|
||||||
|
if (cursorMode) {
|
||||||
|
renderer.fillRect(cursorPixelX, cursorLineY, cursorCharWidth, lineHeight, true);
|
||||||
|
if (cursorPos < displayText.length()) {
|
||||||
|
const char buf[2] = {displayText[cursorPos], '\0'};
|
||||||
|
renderer.drawText(UI_12_FONT_ID, cursorPixelX, cursorLineY, buf, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
renderer.drawLine(cursorPixelX, cursorLineY + lineHeight - 1, cursorPixelX + cursorCharWidth,
|
||||||
|
cursorLineY + lineHeight - 1, 2, true);
|
||||||
|
}
|
||||||
|
|
||||||
const int keyHeight = metrics.keyboardKeyHeight;
|
const int keyHeight = metrics.keyboardKeyHeight;
|
||||||
const int bottomKeyHeight = metrics.keyboardBottomKeyHeight;
|
const int bottomKeyHeight = metrics.keyboardBottomKeyHeight;
|
||||||
const int keySpacing = metrics.keyboardKeySpacing;
|
const int keySpacing = metrics.keyboardKeySpacing;
|
||||||
@@ -293,9 +367,9 @@ void KeyboardEntryActivity::render(RenderLock&&) {
|
|||||||
|
|
||||||
const char primaryBuf[2] = {primaryChar, '\0'};
|
const char primaryBuf[2] = {primaryChar, '\0'};
|
||||||
const char secondaryBuf[2] = {secondaryChar, '\0'};
|
const char secondaryBuf[2] = {secondaryChar, '\0'};
|
||||||
|
|
||||||
const bool showSecondary = !symMode && row == 0 && secondaryChar != '\0';
|
const bool showSecondary = !symMode && row == 0 && secondaryChar != '\0';
|
||||||
GUI.drawKeyboardKey(renderer, Rect{keyX, rowY, keyWidth, keyHeight}, primaryBuf, isSelected,
|
const bool activeKeySelected = isSelected && !cursorMode;
|
||||||
|
GUI.drawKeyboardKey(renderer, Rect{keyX, rowY, keyWidth, keyHeight}, primaryBuf, activeKeySelected,
|
||||||
showSecondary ? secondaryBuf : nullptr);
|
showSecondary ? secondaryBuf : nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,8 +401,25 @@ void KeyboardEntryActivity::render(RenderLock&&) {
|
|||||||
const int keyX = leftMargin + i * (bottomKeyWidth + bkSpacing);
|
const int keyX = leftMargin + i * (bottomKeyWidth + bkSpacing);
|
||||||
const bool isSelected = bottomSelected && i == selectedCol;
|
const bool isSelected = bottomSelected && i == selectedCol;
|
||||||
|
|
||||||
|
const bool activeKeySelected = isSelected && !cursorMode;
|
||||||
GUI.drawKeyboardKey(renderer, Rect{keyX, bottomRowY, bottomKeyWidth, bottomKeyHeight}, bottomKeys[i].label,
|
GUI.drawKeyboardKey(renderer, Rect{keyX, bottomRowY, bottomKeyWidth, bottomKeyHeight}, bottomKeys[i].label,
|
||||||
isSelected, nullptr, bottomKeys[i].themeType);
|
activeKeySelected, nullptr, bottomKeys[i].themeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursorMode) {
|
||||||
|
int selKeyX, selKeyY, selKeyW, selKeyH;
|
||||||
|
if (isBottomRow(selectedRow)) {
|
||||||
|
selKeyX = leftMargin + selectedCol * (bottomKeyWidth + bkSpacing);
|
||||||
|
selKeyY = bottomRowY;
|
||||||
|
selKeyW = bottomKeyWidth;
|
||||||
|
selKeyH = bottomKeyHeight;
|
||||||
|
} else {
|
||||||
|
selKeyX = leftMargin + selectedCol * (keyWidth + keySpacing);
|
||||||
|
selKeyY = keyboardStartY + selectedRow * (keyHeight + keySpacing);
|
||||||
|
selKeyW = keyWidth;
|
||||||
|
selKeyH = keyHeight;
|
||||||
|
}
|
||||||
|
renderer.drawRect(selKeyX - 1, selKeyY - 1, selKeyW + 2, selKeyH + 2, 2, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_LEFT), tr(STR_DIR_RIGHT));
|
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_LEFT), tr(STR_DIR_RIGHT));
|
||||||
|
|||||||
@@ -48,6 +48,13 @@ class KeyboardEntryActivity : public Activity {
|
|||||||
bool confirmHeld = false;
|
bool confirmHeld = false;
|
||||||
bool confirmLongHandled = false;
|
bool confirmLongHandled = false;
|
||||||
|
|
||||||
|
bool cursorMode = false;
|
||||||
|
size_t cursorPos = 0;
|
||||||
|
bool upHeld = false;
|
||||||
|
bool upLongHandled = false;
|
||||||
|
bool downHeld = false;
|
||||||
|
bool downLongHandled = false;
|
||||||
|
|
||||||
void onComplete(std::string text);
|
void onComplete(std::string text);
|
||||||
void onCancel();
|
void onCancel();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user