diff --git a/CHANGELOG b/CHANGELOG index 9a7b7e80..79791722 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,13 @@ +1.6.0-bacon4 + QWERTY keyboard name entry for new projects + * Press A on the project name field to enter QWERTY keyboard mode + * Navigate the on-screen keyboard with D-PAD/arrows + * Press A to input the selected character + * Press B to backspace + * Press L/R to move the text cursor left/right + * Press START or select OK to exit keyboard mode + * All keyboard navigation logic extracted to reusable KeyboardLayout.h utility + 1.6.0-bacon2 Interpolate values in tables and phrases R + B on single-column selection will fill values from lowest to highest diff --git a/docs/wiki/What-is-LittlePiggyTracker.md b/docs/wiki/What-is-LittlePiggyTracker.md index 52c6fd3c..83b4f86b 100644 --- a/docs/wiki/What-is-LittlePiggyTracker.md +++ b/docs/wiki/What-is-LittlePiggyTracker.md @@ -51,8 +51,24 @@ After that you can copy additional wavs to the lgptRoot/lgptProject/samples dire ## New project -When creating a new project, use the Random button to generate a random name. Generate a new name with Random or edit it manually selecting characters with A and pressing up/down -Attempting to create a project with the same name in the same location produces a notification that this operation is denied +When creating a new project, you have several options for naming: + +**Random Name Generation:** +- Select the "Random" button and press A to generate a random name + +**QWERTY Keyboard Entry:** +- Move to the name field and press A to enter QWERTY keyboard mode +- An on-screen keyboard will appear with these controls: + - **D-PAD/Arrows:** Navigate the keyboard + - **A:** Input the selected character + - **B:** Backspace (delete character) + - **L/R:** Move the text cursor left/right within your project name + - **START or OK key:** Exit keyboard mode and return to the dialog +- The keyboard includes: + - Numbers (0-9) + - Uppercase and lowercase letters (A-Z, a-z) + - Special characters (@ | - _ < > ? ,) + - Space bar, backspace, and OK (done) buttons on the bottom row ## Multiple Projects diff --git a/docs/wiki/tips_and_tricks.md b/docs/wiki/tips_and_tricks.md index 470e2a87..15d84b16 100644 --- a/docs/wiki/tips_and_tricks.md +++ b/docs/wiki/tips_and_tricks.md @@ -1,3 +1,20 @@ +# Project Naming with QWERTY Keyboard + +Since version 1.6.0, you can use an on-screen QWERTY keyboard when creating new projects: + +**Quick Tips:** +- Press A on the project name to enter keyboard mode +- The keyboard cursor will jump to the character under your text cursor unless it's a space +- Use L/R shoulder buttons to move through your project name +- Erase with B and exit with Start + +**Keyboard Layout:** +The on-screen keyboard is organized like this: +- Numbers +- Uppercase (A-Q + extra characters) +- Lowercase (a-q + extra characters) +- Special [Space] [Erase] [Done] + # Delays and Echoes ## Simulating LSDj's D command diff --git a/projects/lgpt.vcxproj b/projects/lgpt.vcxproj index e6f588df..24ee5329 100644 --- a/projects/lgpt.vcxproj +++ b/projects/lgpt.vcxproj @@ -440,6 +440,7 @@ + diff --git a/projects/lgptest.dev b/projects/lgptest.dev index e877c121..e3e0ef5b 100644 --- a/projects/lgptest.dev +++ b/projects/lgptest.dev @@ -2937,7 +2937,6 @@ Priority=1000 OverrideBuildCmd=0 BuildCmd= - [Unit288] FileName=..\sources\Application\utils\RandomNames.h CompileCpp=1 @@ -2948,3 +2947,13 @@ Priority=1000 OverrideBuildCmd=0 BuildCmd= +[Unit289] +FileName=..\sources\Application\utils\KeyboardLayout.h +CompileCpp=1 +Folder=Application/Utils +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + diff --git a/sources/Application/Model/Project.h b/sources/Application/Model/Project.h index 5b8f5c1a..0004b196 100644 --- a/sources/Application/Model/Project.h +++ b/sources/Application/Model/Project.h @@ -20,7 +20,7 @@ #define PROJECT_NUMBER "1" #define PROJECT_RELEASE "6" -#define BUILD_COUNT "0-bacon2" +#define BUILD_COUNT "0-bacon4" #define MAX_TAP 3 @@ -56,14 +56,14 @@ class Project: public Persistent,public VariableContainer,I_Observer { void LoadFirstGen(const char *root); protected: - void buildMidiDeviceList() ; -private: - InstrumentBank *instrumentBank_ ; - char **midiDeviceList_ ; - int midiDeviceListSize_ ; - int tempoNudge_ ; - unsigned long lastTap_[MAX_TAP] ; - unsigned int tempoTapCount_; -} ; + void buildMidiDeviceList(); +private: + InstrumentBank *instrumentBank_; + char **midiDeviceList_; + int midiDeviceListSize_; + int tempoNudge_; + unsigned long lastTap_[MAX_TAP]; + unsigned int tempoTapCount_; +}; #endif diff --git a/sources/Application/Utils/KeyboardLayout.h b/sources/Application/Utils/KeyboardLayout.h new file mode 100644 index 00000000..c6ae7704 --- /dev/null +++ b/sources/Application/Utils/KeyboardLayout.h @@ -0,0 +1,102 @@ +#ifndef _KEYBOARD_LAYOUT_H_ +#define _KEYBOARD_LAYOUT_H_ + +#include + +// Keyboard layout configuration +#define SPACE_ROW 7 +#define KEYBOARD_ROWS (SPACE_ROW + 1) + +#define SPACE_START 0 +#define SPACE_END 3 +#define BACK_START 4 +#define BACK_END 6 +#define DONE_START 8 +#define DONE_END 10 + +static const char* keyboardLayout[] = { + "1234567890", + "QWERTYUIOP", + "ASDFGHJKL@", + "ZXCVBNM,?>", + "qwertyuiop", + "asdfghjkl|", + "zxcvbnm-_<", + "[_] <- OK" +}; + +// Helper functions for special row section detection +inline bool isInSpaceSection(int col) { return col < SPACE_END; } +inline bool isInBackSection(int col) { return col >= BACK_START && col < BACK_END; } +inline bool isInDoneSection(int col) { return col >= DONE_START; } + +// Get the character at a specific keyboard position +inline char getKeyAtPosition(int row, int col) { + if (row < 0 || row >= KEYBOARD_ROWS) return '\0'; + const char* rowStr = keyboardLayout[row]; + + // Handle special row with SPC, BACK, and DONE + if (row == SPACE_ROW) { + if (col >= 0 && col < SPACE_END) + return ' '; // [_] (space) + if (col >= BACK_START && col < BACK_END) return '\b'; // <- (backspace) + if (col >= DONE_START && col < DONE_END) return '\r'; // OK (carriage return) + return '\0'; + } + + int len = strlen(rowStr); + if (col < 0 || col >= len) return '\0'; + return rowStr[col]; +} + +// Find a character's position in the keyboard layout +inline void findCharacterInKeyboard(char ch, int &outRow, int &outCol) { + if (ch == ' ') return; // Skip space character + + // Search for character in keyboard layout (excluding special row) + for (int row = 0; row < SPACE_ROW; row++) { + const char* rowStr = keyboardLayout[row]; + int len = strlen(rowStr); + for (int col = 0; col < len; col++) { + if (rowStr[col] == ch) { + outRow = row; + outCol = col; + return; + } + } + } + // Character not found, don't change position +} + +// Clamp keyboard column to valid range for current row +inline void clampKeyboardColumn(int row, int& col) { + if (row == SPACE_ROW) { + if (col < SPACE_END) col = SPACE_START; + else if (col <= BACK_END) col = BACK_START; + else col = DONE_START; + } else { + int maxCol = strlen(keyboardLayout[row]) - 1; + if (col > maxCol) col = 0; + } +} + +// Cycle keyboard column left (-1) or right (+1) within current row +inline void cycleKeyboardColumn(int row, int direction, int& col) { + if (row == SPACE_ROW) { + if (direction < 0) { // LEFT + if (isInSpaceSection(col)) + col = DONE_START; + else if (isInBackSection(col)) col = SPACE_START; + else col = BACK_START; + } else { // RIGHT + if (isInBackSection(col)) col = DONE_START; + else if (isInDoneSection(col)) col = SPACE_START; + else col = BACK_START; + } + } else { + int maxCol = strlen(keyboardLayout[row]) - 1; + col = (col + direction + maxCol + 1) % (maxCol + 1); + } +} + +#endif diff --git a/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp b/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp index 132a63ca..1f4d7649 100644 --- a/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp +++ b/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp @@ -1,5 +1,6 @@ #include "NewProjectDialog.h" +#include "Application/Utils/KeyboardLayout.h" #include "Application/Utils/RandomNames.h" static char *buttonText[BUTTONS_LENGTH] = {(char *)"Random", (char *)"Ok", @@ -12,39 +13,88 @@ NewProjectDialog::NewProjectDialog(View &view, Path currentPath) NewProjectDialog::~NewProjectDialog() {} +// Move text cursor left (-1) or right (+1) and update keyboard position +void NewProjectDialog::moveCursor(int direction) { + int newPos = currentChar_ + direction; + if (newPos >= 0 && newPos < MAX_NAME_LENGTH) { + currentChar_ = newPos; + findCharacterInKeyboard(name_[currentChar_], keyboardRow_, + keyboardCol_); + } +} + void NewProjectDialog::DrawView() { - SetWindow(DIALOG_WIDTH,5) ; + SetWindow(DIALOG_WIDTH, keyboardMode_ ? 15 : 5); - GUITextProperties props ; + GUITextProperties props; - SetColor(CD_NORMAL) ; + SetColor(CD_NORMAL); // Draw string int x = (DIALOG_WIDTH - MAX_NAME_LENGTH) / 3; char buffer[2]; - buffer[1]=0 ; - for (int i=0;i 0) { + currentChar_--; + name_[currentChar_] = ' '; + } + } else if (ch == '\r') { + // END key: exit keyboard mode (same as START) + keyboardMode_ = false; + isDirty_ = true; + return; + } else if (ch != '\0') { + name_[currentChar_] = ch; + lastChar_ = ch; + if (currentChar_ < MAX_NAME_LENGTH - 1) { + currentChar_++; + findCharacterInKeyboard(name_[currentChar_], keyboardRow_, + keyboardCol_); + } + } + isDirty_ = true; + return; + } else if (mask == EPBM_B) { + // Backspace: delete character and move cursor left + if (currentChar_ > 0) { + currentChar_--; + name_[currentChar_] = ' '; + isDirty_ = true; + } + return; + } else if (mask == EPBM_L) { + // Move cursor left + moveCursor(-1); + isDirty_ = true; + return; + } else if (mask == EPBM_R) { + // Move cursor right + moveCursor(1); + isDirty_ = true; + return; + } else if (mask == EPBM_UP) { + keyboardRow_ = (keyboardRow_ - 1 + KEYBOARD_ROWS) % KEYBOARD_ROWS; + clampKeyboardColumn(keyboardRow_, keyboardCol_); + isDirty_ = true; + return; + } else if (mask == EPBM_DOWN) { + keyboardRow_ = (keyboardRow_ + 1) % KEYBOARD_ROWS; + clampKeyboardColumn(keyboardRow_, keyboardCol_); + isDirty_ = true; + return; + } else if (mask == EPBM_LEFT) { + cycleKeyboardColumn(keyboardRow_, -1, keyboardCol_); + isDirty_ = true; + return; + } else if (mask == EPBM_RIGHT) { + cycleKeyboardColumn(keyboardRow_, 1, keyboardCol_); + isDirty_ = true; + return; + } else if (mask == EPBM_START) { + keyboardMode_ = false; + isDirty_ = true; + return; + } + return; + } else if (mask & EPBM_A) { + if (mask == EPBM_A) { + std::string randomName = ""; + switch (selected_) { + case 0: + // Toggle keyboard mode + keyboardMode_ = !keyboardMode_; + // When entering keyboard mode, jump to current character + if (keyboardMode_) { + findCharacterInKeyboard(name_[currentChar_], keyboardRow_, + keyboardCol_); + } + isDirty_ = true; + break; + case 1: + do { + randomName = getRandomName(); + std::fill(name_ + randomName.length(), + name_ + sizeof(name_) / sizeof(name_[0]), ' '); + strncpy(name_, randomName.c_str(), randomName.length()); + lastChar_ = currentChar_ = randomName.length() - 1; + } while (currentPath_.Descend(GetName()).Exists()); + isDirty_ = true; + break; + case 2: + if (currentPath_.Descend(GetName()).Exists()) { + std::string res("Name " + std::string(name_) + " busy"); + View::SetNotification(res.c_str(), -6); + } else { + EndModal(1); + } + break; + case 3: + EndModal(0); + break; + } + } + if (mask & EPBM_UP) { + name_[currentChar_]+=1; lastChar_=name_[currentChar_] ; isDirty_=true ; - } - if (mask&EPBM_DOWN) { + } + if (mask&EPBM_DOWN) { name_[currentChar_]-=1; lastChar_=name_[currentChar_] ; isDirty_=true ; - } - } else { - - // R Modifier - - if (mask & EPBM_R) { - } else { - // No modifier - if (mask == EPBM_UP) { - selected_ = (selected_ == 0) ? 1 : 0; - isDirty_ = true; - } - if (mask == EPBM_DOWN) { - selected_ = (selected_ == 0) ? 1 : 0; - isDirty_ = true; - } - - if (mask == EPBM_LEFT) { - switch (selected_) { - case 0: - if (currentChar_ > 0) - currentChar_--; - break; - case 1: - case 2: - case 3: - if (selected_ > 0) - selected_--; - break; - } - isDirty_ = true; - } - if (mask == EPBM_RIGHT) { - switch (selected_) { - case 0: - if (currentChar_ < MAX_NAME_LENGTH - 1) - currentChar_++; - else - selected_++; - break; - case 1: - case 2: - case 3: - if (selected_ < BUTTONS_LENGTH) - selected_++; - break; - } - isDirty_ = true; - } - } - } + } + } else { + + // R Modifier + + if (mask & EPBM_R) { + } else { + // No modifier + if (mask == EPBM_UP) { + selected_ = (selected_ == 0) ? 1 : 0; + isDirty_ = true; + } + if (mask == EPBM_DOWN) { + selected_ = (selected_ == 0) ? 1 : 0; + isDirty_ = true; + } + + if (mask == EPBM_LEFT) { + switch (selected_) { + case 0: + if (currentChar_ > 0) + currentChar_--; + break; + case 1: + case 2: + case 3: + if (selected_ > 0) + selected_--; + break; + } + isDirty_ = true; + } + if (mask == EPBM_RIGHT) { + switch (selected_) { + case 0: + if (currentChar_ < MAX_NAME_LENGTH - 1) + currentChar_++; + else + selected_++; + break; + case 1: + case 2: + case 3: + if (selected_ < BUTTONS_LENGTH) + selected_++; + break; + } + isDirty_ = true; + } + } } }; - std::string NewProjectDialog::GetName() { for (int i = MAX_NAME_LENGTH; i >= 0; i--) { if (name_[i]==' ') { diff --git a/sources/Application/Views/ModalDialogs/NewProjectDialog.h b/sources/Application/Views/ModalDialogs/NewProjectDialog.h index 457f8692..fa159cac 100644 --- a/sources/Application/Views/ModalDialogs/NewProjectDialog.h +++ b/sources/Application/Views/ModalDialogs/NewProjectDialog.h @@ -1,6 +1,7 @@ #ifndef _NEW_PROJECT_DIALOG_H_ #define _NEW_PROJECT_DIALOG_H_ +#include "Application/Utils/KeyboardLayout.h" #include "Application/Views/BaseClasses/ModalView.h" #include @@ -25,6 +26,9 @@ class NewProjectDialog:public ModalView { int lastChar_; char name_[MAX_NAME_LENGTH + 1]; int currentChar_; -} ; - + bool keyboardMode_; + int keyboardRow_; + int keyboardCol_ ; + void moveCursor(int direction); +}; #endif