diff --git a/README.md b/README.md index 629f2d3f2c..8c96340375 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ See [BUILDING.md](BUILDING.md) * Cmd/Ctrl-M: change *m*onitor * Cmd/Ctrl-F: toggle *f*ull screen * Mouse Scroll Up / Down: next or previous preset (hard transition) +* Return: search for preset (RETURN or ESCAPE to exit search) * Space: lock current preset diff --git a/src/libprojectM/KeyHandler.cpp b/src/libprojectM/KeyHandler.cpp index 7613cb07ae..857deea5e1 100755 --- a/src/libprojectM/KeyHandler.cpp +++ b/src/libprojectM/KeyHandler.cpp @@ -120,17 +120,30 @@ void projectM::default_key_handler( projectMEvent event, projectMKeycode keycode switch (keycode) { case PROJECTM_K_HOME: - if (renderer->showmenu) { // pageup only does something when the preset menu is active. - selectPreset(0); // jump to top of presets. + if (renderer->showmenu) { + if (!isTextInputActive()) { + selectPreset(0); // jump to top of presets. + } + else { + renderer->m_activePresetID = 1; // jump to top of search results. + selectPresetByName(renderer->m_presetList[0].name,true); + } } break; case PROJECTM_K_END: - if (renderer->showmenu) { // pageup only does something when the preset menu is active. - selectPreset(m_presetLoader->size() - 1); // jump to bottom of presets. + if (renderer->showmenu) { + if (!isTextInputActive()) { + selectPreset(m_presetLoader->size() - 1); // jump to bottom of presets. + } + else { + renderer->m_activePresetID = renderer->m_presetList.size(); // jump to top of search results. + selectPresetByName(renderer->m_presetList[renderer->m_activePresetID - 1].name,true); + } } break; case PROJECTM_K_PAGEUP: - if (renderer->showmenu) { // pageup only does something when the preset menu is active. + if (isTextInputActive()) break; // don't handle this key if search menu is up. + if (renderer->showmenu) { int upPreset = m_presetPos->lastIndex() - (renderer->textMenuPageSize / 2.0f); // jump up by page size / 2 if (upPreset < 0) // handle lower boundary upPreset = m_presetLoader->size() - 1; @@ -138,7 +151,8 @@ void projectM::default_key_handler( projectMEvent event, projectMKeycode keycode } break; case PROJECTM_K_PAGEDOWN: - if (renderer->showmenu) { // pagedown only does something when the preset menu is active. + if (isTextInputActive()) break; // don't handle this key if search menu is up. + if (renderer->showmenu) { int downPreset = m_presetPos->lastIndex() + (renderer->textMenuPageSize / 2.0f); // jump down by page size / 2 if (downPreset >= (m_presetLoader->size() - 1)) // handle upper boundary downPreset = 0; @@ -166,15 +180,19 @@ void projectM::default_key_handler( projectMEvent event, projectMKeycode keycode } break; case PROJECTM_K_h: + if (isTextInputActive(true)) break; // disable when searching. renderer->showhelp = !renderer->showhelp; renderer->showstats = false; renderer->showmenu = false; + break; case PROJECTM_K_F1: + if (isTextInputActive(true)) break; // disable when searching. renderer->showhelp = !renderer->showhelp; renderer->showstats = false; renderer->showmenu = false; break; case PROJECTM_K_y: + if (isTextInputActive(true)) break; // disable when searching. this->setShuffleEnabled(!this->isShuffleEnabled()); if (this->isShuffleEnabled()) { renderer->setToastMessage("Shuffle Enabled"); @@ -183,8 +201,8 @@ void projectM::default_key_handler( projectMEvent event, projectMKeycode keycode renderer->setToastMessage("Shuffle Disabled"); } break; - case PROJECTM_K_F5: + if (isTextInputActive(true)) break; // disable when searching. renderer->showfps = !renderer->showfps; // Initialize counters and reset frame count. renderer->lastTimeFPS = duration_cast(system_clock::now().time_since_epoch()); @@ -197,6 +215,7 @@ void projectM::default_key_handler( projectMEvent event, projectMKeycode keycode } break; case PROJECTM_K_F4: + if (isTextInputActive(true)) break; // disable when searching. renderer->showstats = !renderer->showstats; if (renderer->showstats) { renderer->showhelp = false; @@ -204,6 +223,7 @@ void projectM::default_key_handler( projectMEvent event, projectMKeycode keycode } break; case PROJECTM_K_F3: { + if (isTextInputActive(true)) break; // disable when searching. renderer->showpreset = !renderer->showpreset; // Hide FPS from screen and replace it with preset name. if (renderer->showpreset) @@ -225,7 +245,9 @@ void projectM::default_key_handler( projectMEvent event, projectMKeycode keycode break; case PROJECTM_K_ESCAPE: { - // exit( 1 ); + renderer->showsearch = false; // hide input menu + setShuffleEnabled(renderer->shuffletrack); // restore shuffle + renderer->showmenu = false; // hide input break; } case PROJECTM_K_f: @@ -238,6 +260,7 @@ void projectM::default_key_handler( projectMEvent event, projectMKeycode keycode break; case PROJECTM_K_H: case PROJECTM_K_m: + if (isTextInputActive(true)) break; // disable when searching. renderer->showmenu = !renderer->showmenu; if (renderer->showmenu) { renderer->showhelp = false; @@ -246,53 +269,71 @@ void projectM::default_key_handler( projectMEvent event, projectMKeycode keycode } break; case PROJECTM_K_M: + if (isTextInputActive(true)) break; // disable when searching. renderer->showmenu = !renderer->showmenu; if (renderer->showmenu) { - renderer->showhelp=false; - renderer->showstats=false; + renderer->showhelp = false; + renderer->showstats = false; populatePresetMenu(); } - break; + break; case PROJECTM_K_n: - selectNext(true); - break; + if (isTextInputActive(true)) break; // disable when searching. + selectNext(true); + break; case PROJECTM_K_N: - selectNext(false); - break; + if (isTextInputActive(true)) break; // disable when searching. + selectNext(false); + break; case PROJECTM_K_r: - selectRandom(true); - break; + if (isTextInputActive(true)) break; // disable when searching. + selectRandom(true); + break; case PROJECTM_K_R: - selectRandom(false); - break; + if (isTextInputActive(true)) break; // disable when searching. + selectRandom(false); + break; case PROJECTM_K_p: - selectPrevious(true); - break; + selectPrevious(true); + break; case PROJECTM_K_P: case PROJECTM_K_BACKSPACE: - selectPrevious(false); - break; + selectPrevious(false); + break; case PROJECTM_K_l: - setPresetLock(!isPresetLocked()); - break; + if (isTextInputActive(true)) break; // disable when searching. + setPresetLock(!isPresetLocked()); + break; case PROJECTM_K_s: - renderer->studio = !renderer->studio; + renderer->studio = !renderer->studio; case PROJECTM_K_i: break; - case PROJECTM_K_z: - break; + case PROJECTM_K_RETURN: + renderer->toggleSearchText(); + if (renderer->showsearch) { + renderer->shuffletrack = this->isShuffleEnabled(); // track previous shuffle state. + setShuffleEnabled(false); // disable shuffle + renderer->showhelp = false; + renderer->showstats = false; + renderer->showmenu = true; + populatePresetMenu(); + } else { + setShuffleEnabled(renderer->shuffletrack); // restore shuffle + renderer->showmenu = false; + } + break; case PROJECTM_K_0: -// nWaveMode=0; - break; +// nWaveMode=0; + break; case PROJECTM_K_6: -// nWaveMode=6; - break; +// nWaveMode=6; + break; case PROJECTM_K_7: -// nWaveMode=7; - break; +// nWaveMode=7; + break; case PROJECTM_K_t: - break; + break; case PROJECTM_K_EQUALS: case PROJECTM_K_PLUS: diff --git a/src/libprojectM/PresetLoader.cpp b/src/libprojectM/PresetLoader.cpp index 2b4ef100f5..4731484f0e 100644 --- a/src/libprojectM/PresetLoader.cpp +++ b/src/libprojectM/PresetLoader.cpp @@ -149,6 +149,19 @@ const std::string & PresetLoader::getPresetName ( PresetIndex index ) const return _presetNames[index]; } + +// Get vector of preset names +const std::vector &PresetLoader::getPresetNames() const +{ + return _presetNames; +} + +// Get the preset index given a name +const unsigned int PresetLoader::getPresetIndex(std::string &name) const +{ + return find(_presetNames.begin(), _presetNames.end(), name) - _presetNames.begin(); +} + int PresetLoader::getPresetRating ( PresetIndex index, const PresetRatingType ratingType ) const { return _ratings[ratingType][index]; diff --git a/src/libprojectM/PresetLoader.hpp b/src/libprojectM/PresetLoader.hpp index df314387c4..6de901b702 100644 --- a/src/libprojectM/PresetLoader.hpp +++ b/src/libprojectM/PresetLoader.hpp @@ -70,6 +70,12 @@ class PresetLoader { /// Get a preset name given an index const std::string & getPresetName ( PresetIndex index) const; + /// Get vector of preset names + const std::vector & getPresetNames() const; + + /// Get the preset index given a name + const unsigned int getPresetIndex(std::string &name) const; + /// Returns the number of presets in the active directory inline std::size_t size() const { return _entries.size(); diff --git a/src/libprojectM/Renderer/Renderer.cpp b/src/libprojectM/Renderer/Renderer.cpp index 785563bf3a..5d69c1b891 100644 --- a/src/libprojectM/Renderer/Renderer.cpp +++ b/src/libprojectM/Renderer/Renderer.cpp @@ -27,13 +27,13 @@ class Preset; void Renderer::drawText(const char* string, GLfloat x, GLfloat y, GLfloat scale, - int horizontalAlignment = GLT_LEFT, int verticalAlignment = GLT_TOP, float r = 1.0f, float b = 1.0f, float g = 1.0f, float a = 1.0f) + int horizontalAlignment = GLT_LEFT, int verticalAlignment = GLT_TOP, float r = 1.0f, float b = 1.0f, float g = 1.0f, float a = 1.0f, bool highlightable = false) { - drawText(this->title_font, string, x, y, scale, horizontalAlignment, verticalAlignment, r, g, b, a); + drawText(this->title_font, string, x, y, scale, horizontalAlignment, verticalAlignment, r, g, b, a, highlightable); } void Renderer::drawText(GLTtext* text, const char* string, GLfloat x, GLfloat y, GLfloat scale, - int horizontalAlignment = GLT_LEFT, int verticalAlignment = GLT_TOP, float r = 1.0f, float b = 1.0f, float g = 1.0f, float a = 1.0f) + int horizontalAlignment = GLT_LEFT, int verticalAlignment = GLT_TOP, float r = 1.0f, float b = 1.0f, float g = 1.0f, float a = 1.0f, bool highlightable = false) { // Initialize glText gltInit(); @@ -69,24 +69,16 @@ void Renderer::drawText(GLTtext* text, const char* string, GLfloat x, GLfloat y, if (windowWidth > textWidth) { // redraw without transparency - gltColor(r, g, b, a); - gltSetText(text, string); - gltDrawText2DAligned(text, x, y, scale, horizontalAlignment, verticalAlignment); - } - else { + if (textHighlightable(highlightable)) + { + drawText(text, string, searchText().c_str(), x, y, scale, horizontalAlignment, verticalAlignment, r, g, b, a, highlightable); + } else { + gltColor(r, g, b, a); + gltSetText(text, string); + gltDrawText2DAligned(text, x, y, scale, horizontalAlignment, verticalAlignment); + } + } else { // if the text is greater than the window width, we have a problem. - - // Option 1: Reduce text length until it fits. - // If it's a single line of text then reduce the text and add a "..." to the end. - // Before: Geiss & Sperl - Feedback (projectM idle HDR mix) - // After: Geiss & Sperl - Feed... - // - // If it's multiline then just cut the text off. - // Before: F1: This help menu - // UP: Increase Beat Sensitivity - // After : F1: This help menu - // UP: Increase Beat Sensi - std::string substring(string); while (textWidth > windowWidth) { substring.pop_back(); @@ -103,21 +95,17 @@ void Renderer::drawText(GLTtext* text, const char* string, GLfloat x, GLfloat y, substring.pop_back(); substring += "..."; } - string = substring.c_str(); - // Option 2: Reduce the scale (size) of the text until it fits. - /* - while (textWidth > windowWidth) { - scale = scale - 0.1; + if (textHighlightable(highlightable)) + { + drawText(text, substring.c_str(), searchText().c_str(), x, y, scale, horizontalAlignment, verticalAlignment, r, g, b, a, highlightable); + } else { + string = substring.c_str(); + // Redraw now that the text fits. + gltColor(r, g, b, a); + gltSetText(text, string); gltDrawText2DAligned(text, x, y, scale, horizontalAlignment, verticalAlignment); - textWidth = gltGetTextWidth(text, scale); } - */ - - // Redraw now that the text fits. - gltColor(r, g, b, a); - gltSetText(text, string); - gltDrawText2DAligned(text, x, y, scale, horizontalAlignment, verticalAlignment); } // Finish drawing text @@ -130,6 +118,41 @@ void Renderer::drawText(GLTtext* text, const char* string, GLfloat x, GLfloat y, gltTerminate(); } +// draw text with search term a/k/a needle & highlight text +void Renderer::drawText(GLTtext* text, const char* string, const char* needle, GLfloat x, GLfloat y, GLfloat scale, int horizontalAlignment = GLT_LEFT, int verticalAlignment = GLT_TOP, float r = 1.0f, float b = 1.0f, float g = 1.0f, float a = 1.0f, bool highlightable = false) { + int offset = x; + std::string str_find = string; + for( size_t pos = 0; ; pos += str_find.length() ) { + // find search term + pos = str_find.find(needle); + + // draw everything normal, up to search term. + gltColor(r, g, b, a); + gltSetText(text, str_find.substr(0,pos).c_str()); + gltDrawText2DAligned(text, x, y, scale, horizontalAlignment, verticalAlignment); + + // highlight search term + GLfloat textWidth = gltGetTextWidth(text, scale); + offset = offset + textWidth; + gltColor(1.0f, 0.0f, 1.0f, 1.0f); + gltSetText(text, searchText().c_str()); + gltDrawText2DAligned(text, offset, y, scale, horizontalAlignment, verticalAlignment); + + // draw rest of name, normally + textWidth = gltGetTextWidth(text, scale); + offset = offset + textWidth; + gltColor(r, g, b, a); + gltSetText(text, str_find.substr(pos+searchText().length(),str_find.length()).c_str()); + gltDrawText2DAligned(text, offset, y, scale, horizontalAlignment, verticalAlignment); + break; // first search hit is useful enough. + } +} + +bool Renderer::textHighlightable(bool highlightable) { + if (highlightable && showsearch && searchText().length() > 1) + return true; + return false; +} #endif /** USE_TEXT_MENU */ @@ -149,9 +172,11 @@ Renderer::Renderer(int width, int height, int gx, int gy, BeatDetect* _beatDetec this->showtitle = false; this->showpreset = false; this->showhelp = false; + this->showsearch = false; this->showmenu = false; this->showstats = false; this->studio = false; + this->m_activePresetID = 0; this->realfps = 0; /* Set up the v xoffset and vy offset to 0 which is normal Only used for VR */ this->vstartx = 0; @@ -421,6 +446,8 @@ void Renderer::Pass2(const Pipeline& pipeline, const PipelineContext& pipelineCo if (this->showfps == true) draw_fps(); // this->realfps + if (this->showsearch == true) + draw_search(); if (this->showmenu == true) draw_menu(); if (this->showpreset == true) @@ -769,6 +796,36 @@ void Renderer::touchDestroyAll() waveformList.clear(); } +// turn search menu on / off +void Renderer::toggleSearchText() { + this->showsearch = !this->showsearch; + if (this->showsearch) + { + this->showfps = false; + this->showtitle = false; + } +} + +// search based on new key input +void Renderer::setSearchText(const std::string& theValue) +{ + m_searchText = m_searchText + theValue; +} + +// reset search text backspace (reset) +void Renderer::resetSearchText() +{ + m_searchText = ""; +} + +// search text backspace (delete a key) +void Renderer::deleteSearchText() +{ + if (m_searchText.length() >= 1) { + m_searchText = m_searchText.substr(0, m_searchText.size() - 1); + } +} + void Renderer::setToastMessage(const std::string& theValue) { // Initialize counters @@ -788,6 +845,17 @@ void Renderer::draw_title_to_screen(bool flip) #endif /** USE_TEXT_MENU */ } +// render search text menu +void Renderer::draw_search() +{ +#ifdef USE_TEXT_MENU + std::string search = "Search: "; + search = search + searchText(); + + drawText(search.c_str(), 30, 20, 2.5); +#endif /** USE_TEXT_MENU */ +} + void Renderer::draw_title() { #ifdef USE_TEXT_MENU @@ -802,13 +870,16 @@ void Renderer::draw_menu() int menu_xOffset = 30; // x axis static point. int menu_yOffset = 60; // y axis start point. float windowHeight = vh; + float alpha = 1.0; + if (this->showsearch) // if search input is up, slightly dim preset menu + alpha = 0.82f; for (auto& it : m_presetList) { // loop over preset buffer - if (menu_yOffset < windowHeight - textMenuLineHeight) { // if we are not at the bottom of the scree, display preset name. + if (menu_yOffset < windowHeight - textMenuLineHeight) { // if we are not at the bottom of the screen, display preset name. if (it.id == m_activePresetID) { // if this is the active preset, add some color. - drawText(it.name.c_str(), menu_xOffset, menu_yOffset , 1.5, GLT_LEFT, 0, 1.0, 0.1, 0.1, 1.0); + drawText(it.name.c_str(), menu_xOffset, menu_yOffset , 1.5, GLT_LEFT, 0, 1.0, 0.1, 0.1, 1.0, true); } else { - drawText(it.name.c_str(), menu_xOffset, menu_yOffset , 1.5, GLT_LEFT, 0, 1.0, 1.0, 1.0, 1.0); + drawText(it.name.c_str(), menu_xOffset, menu_yOffset , 1.5, GLT_LEFT, 0, 1.0, 1.0, 1.0, alpha, true); } } menu_yOffset = menu_yOffset + textMenuLineHeight; // increase line y offset so we can track if we reached the bottom of the screen. diff --git a/src/libprojectM/Renderer/Renderer.hpp b/src/libprojectM/Renderer/Renderer.hpp index e3d1a25389..7cb97754d8 100644 --- a/src/libprojectM/Renderer/Renderer.hpp +++ b/src/libprojectM/Renderer/Renderer.hpp @@ -75,9 +75,12 @@ class Renderer bool showtitle; bool showpreset; bool showhelp; + bool showsearch; bool showmenu; bool showstats; + bool shuffletrack; + bool studio; bool correction; @@ -161,6 +164,8 @@ class Renderer return duration_cast(system_clock::now().time_since_epoch());; } + void toggleSearchText(); + void toggleInput(); void touch(float x, float y, int pressure, int type); void touchDrag(float x, float y, int pressure); void touchDestroy(float x, float y); @@ -168,11 +173,18 @@ class Renderer bool touchedWaveform(float x, float y, int i); void setToastMessage(const std::string& theValue); + void setSearchText(const std::string& theValue); + void resetSearchText(); + void deleteSearchText(); std::string toastMessage() const { return m_toastMessage; } - + + std::string searchText() const { + return m_searchText; + } + private: PerPixelMesh mesh; @@ -183,11 +195,11 @@ class Renderer TimeKeeper *timeKeeperToast; #ifdef USE_TEXT_MENU - - void drawText(GLTtext* text, const char* string, GLfloat x, GLfloat y, GLfloat scale, int horizontalAlignment, - int verticalAlignment, float r, float b, float g, float a); - void drawText(const char* string, GLfloat x, GLfloat y, GLfloat scale, int horizontalAlignment, - int verticalAlignment, float r, float b, float g, float a); + // draw text with search term a/k/a needle & highlight text + void drawText(GLTtext* text, const char* string, const char* needle, GLfloat x, GLfloat y, GLfloat scale, int horizontalAlignment, int verticalAlignment, float r, float b, float g, float a, bool highlightable); + void drawText(GLTtext* text, const char* string, GLfloat x, GLfloat y, GLfloat scale, int horizontalAlignment, int verticalAlignment, float r, float b, float g, float a, bool highlightable); + void drawText(const char* string, GLfloat x, GLfloat y, GLfloat scale, int horizontalAlignment, int verticalAlignment, float r, float b, float g, float a, bool highlightable); + bool textHighlightable(bool highlightable); #endif /** USE_TEXT_MENU */ RenderContext renderContext; @@ -197,6 +209,7 @@ class Renderer std::string m_datadir; std::string m_fps; std::string m_toastMessage; + std::string m_searchText; float* p; @@ -252,6 +265,7 @@ class Renderer void draw_help(); void draw_menu(); void draw_preset(); + void draw_search(); void draw_title(); void draw_title_to_screen(bool flip); void draw_title_to_texture(); diff --git a/src/libprojectM/event.h b/src/libprojectM/event.h index 88949f2fc9..1ce0de5fca 100755 --- a/src/libprojectM/event.h +++ b/src/libprojectM/event.h @@ -57,6 +57,8 @@ typedef enum { PROJECTM_K_HOME, PROJECTM_K_END, PROJECTM_K_BACKSPACE, + PROJECTM_K_SLASH, + PROJECTM_K_BACKSLASH, PROJECTM_K_F1, PROJECTM_K_F2, diff --git a/src/libprojectM/projectM.cpp b/src/libprojectM/projectM.cpp index 228bc57c5f..f097749bc4 100755 --- a/src/libprojectM/projectM.cpp +++ b/src/libprojectM/projectM.cpp @@ -787,25 +787,48 @@ void projectM::populatePresetMenu() { if (renderer->showmenu) { // only track a preset list buffer if the preset menu is up. renderer->m_presetList.clear(); // clear preset list buffer from renderer. - renderer->m_activePresetID = m_presetPos->lastIndex(); // tell renderer about the active preset ID (so it can be highlighted) - - int page_start = 0; - if (m_presetPos->lastIndex() != m_presetLoader->size()) - { - page_start = renderer->m_activePresetID; // if it's not the idle preset, then set it to the true value - } - if (page_start < renderer->textMenuPageSize) { - page_start = 0; // if we are on page 1, start at the first preset. - } - if (page_start % renderer->textMenuPageSize == 0) { - // if it's a perfect division of the page size, we are good. - } else { - page_start = page_start - (page_start % renderer->textMenuPageSize); // if not, find closest divisable number for page start + + if(isTextInputActive()) { + // if a searchTerm is active, we will populate the preset menu with search terms instead of the page we are on. + int h = 0; + std::string presetName = renderer->presetName(); + int presetIndex = getSearchIndex(presetName); + for(unsigned int i = 0; i < getPlaylistSize(); i++) { // loop over all presets + if (getPresetName(i).find(renderer->searchText()) != std::string::npos) { // if term matches + if (h < renderer->textMenuPageSize) // limit to just one page, pagination is not needed. + { + h++; + renderer->m_presetList.push_back({ h, getPresetName(i), "" }); // populate the renders preset list. + if (h == presetIndex) + { + renderer->m_activePresetID = h; + } + } + } + } } - int page_end = page_start + renderer->textMenuPageSize; // page end is page start + page size - while (page_start < page_end) { - renderer->m_presetList.push_back({page_start, getPresetName(page_start), ""}); // populate the renders preset list. - page_start++; + else { + // normal preset menu, based on pagination. + renderer->m_activePresetID = m_presetPos->lastIndex(); // tell renderer about the active preset ID (so it can be highlighted) + int page_start = 0; + if (m_presetPos->lastIndex() != m_presetLoader->size()) + { + page_start = renderer->m_activePresetID; // if it's not the idle preset, then set it to the true value + } + if (page_start < renderer->textMenuPageSize) { + page_start = 0; // if we are on page 1, start at the first preset. + } + if (page_start % renderer->textMenuPageSize == 0) { + // if it's a perfect division of the page size, we are good. + } + else { + page_start = page_start - (page_start % renderer->textMenuPageSize); // if not, find closest divisable number for page start + } + int page_end = page_start + renderer->textMenuPageSize; // page end is page start + page size + while (page_start < page_end) { + renderer->m_presetList.push_back({ page_start, getPresetName(page_start), "" }); // populate the renders preset list. + page_start++; + } } } } @@ -854,7 +877,20 @@ void projectM::selectPrevious(const bool hardCut) { if (m_presetChooser->empty()) return; - if (settings().shuffleEnabled && presetHistory.size() >= 1 && presetHistory.back() != m_presetLoader->size() && !renderer->showmenu) { // if randomly browsing presets, "previous" should return to last random preset not the index--. Avoid returning to size() because that's the idle:// preset. + if (isTextInputActive(true) && renderer->m_presetList.size() >= 1) + { + // if search menu is up, previous is based on search terms. + if (renderer->m_activePresetID <= 1) { + // loop to bottom of page is at top + renderer->m_activePresetID = renderer->m_presetList.size(); + selectPresetByName(renderer->m_presetList[renderer->m_activePresetID - 1].name,true); + } + else { + // otherwise move back + renderer->m_activePresetID--; + selectPresetByName(renderer->m_presetList[renderer->m_activePresetID-1].name,true); + } + } else if (settings().shuffleEnabled && presetHistory.size() >= 1 && presetHistory.back() != m_presetLoader->size() && !renderer->showmenu) { // if randomly browsing presets, "previous" should return to last random preset not the index--. Avoid returning to size() because that's the idle:// preset. presetFuture.push_back(m_presetPos->lastIndex()); selectPreset(presetHistory.back()); presetHistory.pop_back(); @@ -871,7 +907,19 @@ void projectM::selectPrevious(const bool hardCut) { void projectM::selectNext(const bool hardCut) { if (m_presetChooser->empty()) return; - if (settings().shuffleEnabled && presetFuture.size() >= 1 && presetFuture.front() != m_presetLoader->size() && !renderer->showmenu) { // if shuffling and we have future presets already stashed then let's go forward rather than truely move randomly. + if (isTextInputActive() && renderer->m_presetList.size() >= 1) // if search is active and there are search results + { + // if search menu is down, next is based on search terms. + if (renderer->m_activePresetID >= renderer->m_presetList.size()) { + // loop to top of page is at bottom + renderer->m_activePresetID = 1; + selectPresetByName(renderer->m_presetList[0].name,true); + } + else { + // otherwise move forward + selectPresetByName(renderer->m_presetList[renderer->m_activePresetID].name,true); + } + } else if (settings().shuffleEnabled && presetFuture.size() >= 1 && presetFuture.front() != m_presetLoader->size() && !renderer->showmenu) { // if shuffling and we have future presets already stashed then let's go forward rather than truely move randomly. presetHistory.push_back(m_presetPos->lastIndex()); selectPreset(presetFuture.back()); presetFuture.pop_back(); @@ -928,6 +976,18 @@ void projectM::setPresetLock ( bool isLocked ) } } +// check if search menu is up and you have search terms (2 chars). nomin means you don't care about search terms. +bool projectM::isTextInputActive( bool nomin ) const +{ + if (renderer->showsearch && (renderer->searchText().length() >= 2 || nomin)) + { + return true; + } + else { + return false; + } +} + bool projectM::isPresetLocked() const { return renderer->noSwitch; @@ -1046,6 +1106,74 @@ void projectM::getMeshSize(int *w, int *h) { *h = _settings.meshY; } +// toggleSearchText +void projectM::toggleSearchText() +{ + if ( renderer ) + renderer->toggleSearchText(); +} + +// get index from search results based on preset name +const unsigned int projectM::getSearchIndex(std::string &name) const +{ + for (auto& it : renderer->m_presetList) { + if (it.name == name) return it.id; + } + return 0; +} + +// get preset index based on preset name +unsigned int projectM::getPresetIndex(std::string& name) const +{ + return m_presetLoader->getPresetIndex(name); +} + +// load preset based on name +void projectM::selectPresetByName(std::string name, bool hardCut) { + unsigned int index = getPresetIndex(name); + if (m_presetChooser->empty()) return; + selectPreset(index); +} + +// update search text based on new keystroke +void projectM::setSearchText(const std::string & searchKey) +{ + if ( renderer ) + renderer->setSearchText(searchKey); + populatePresetMenu(); + if (renderer->m_presetList.size() >= 1) { + std::string topPreset = renderer->m_presetList.front().name; + renderer->m_activePresetID = 1; + selectPresetByName(topPreset); + } +} + +// update search text based on new backspace +void projectM::deleteSearchText() +{ + if ( renderer ) + renderer->deleteSearchText(); + populatePresetMenu(); + if (renderer->m_presetList.size() >= 1) { + renderer->m_activePresetID = 1; + std::string topPreset = renderer->m_presetList.front().name; + selectPresetByName(topPreset); + } +} + +// reset search text +void projectM::resetSearchText() +{ + if ( renderer ) + renderer->resetSearchText(); + populatePresetMenu(); + if (renderer->m_presetList.size() >= 1) { + renderer->m_activePresetID = 1; + std::string topPreset = renderer->m_presetList.front().name; + selectPresetByName(topPreset); + } +} + void projectM::setToastMessage(const std::string & toastMessage) { if ( renderer ) diff --git a/src/libprojectM/projectM.hpp b/src/libprojectM/projectM.hpp index 8477346222..eb965c866f 100644 --- a/src/libprojectM/projectM.hpp +++ b/src/libprojectM/projectM.hpp @@ -188,6 +188,7 @@ class DLLEXPORT projectM void touchDestroy(float x, float y); void touchDestroyAll(); void setHelpText(const std::string & helpText); + void toggleSearchText(); // turn search text input on / off void setToastMessage(const std::string & toastMessage); const Settings & settings() const { return _settings; @@ -229,6 +230,21 @@ class DLLEXPORT projectM /// Returns true if the active preset is locked bool isPresetLocked() const; + /// Returns true if the text based search menu is up. + bool isTextInputActive(bool nomin = false) const; + + unsigned int getPresetIndex(std::string &url) const; + + /// Plays a preset immediately when given preset name + void selectPresetByName(std::string name, bool hardCut = true); + + // search based on keystroke + void setSearchText(const std::string & searchKey); + // delete part of search term (backspace) + void deleteSearchText(); + // reset search term (blank) + void resetSearchText(); + /// Returns index of currently active preset. In the case where the active /// preset was removed from the playlist, this function will return the element /// before active preset (thus the next in order preset is invariant with respect @@ -298,6 +314,8 @@ class DLLEXPORT projectM std::vector presetHistory; std::vector presetFuture; + /// Get the preset index given a name + const unsigned int getSearchIndex(std::string &name) const; void selectPrevious(const bool); void selectNext(const bool); diff --git a/src/libprojectM/sdltoprojectM.h b/src/libprojectM/sdltoprojectM.h index fa55ea2f8c..ddaa282cd9 100755 --- a/src/libprojectM/sdltoprojectM.h +++ b/src/libprojectM/sdltoprojectM.h @@ -194,6 +194,10 @@ inline projectMKeycode sdl2pmKeycode( SDL_Keycode keycode , SDL_Keymod mod ) { \ return PROJECTM_K_UP; case SDLK_RETURN: return PROJECTM_K_RETURN; + case SDLK_SLASH: + return PROJECTM_K_SLASH; + case SDLK_BACKSLASH: + return PROJECTM_K_BACKSLASH; case SDLK_RIGHT: return PROJECTM_K_RIGHT; case SDLK_LEFT: diff --git a/src/projectM-sdl/pmSDL.cpp b/src/projectM-sdl/pmSDL.cpp index 39d09e3b2e..36040f948d 100644 --- a/src/projectM-sdl/pmSDL.cpp +++ b/src/projectM-sdl/pmSDL.cpp @@ -297,121 +297,113 @@ void projectMSDL::keyHandler(SDL_Event *sdl_evt) { // handle keyboard input (for our app first, then projectM) switch (sdl_keycode) { - case SDLK_q: - if (sdl_mod & KMOD_LGUI || sdl_mod & KMOD_RGUI || sdl_mod & KMOD_LCTRL) { - // cmd/ctrl-q = quit - done = 1; - return; - } - break; - case SDLK_i: - if (sdl_mod & KMOD_LGUI || sdl_mod & KMOD_RGUI || sdl_mod & KMOD_LCTRL) - { - toggleAudioInput(); - return; // handled - } - break; - case SDLK_s: - if (sdl_mod & KMOD_LGUI || sdl_mod & KMOD_RGUI || sdl_mod & KMOD_LCTRL) - { - // command-s: [s]tretch monitors - // Stereo requires fullscreen + + case SDLK_q: + if (sdl_mod & KMOD_LGUI || sdl_mod & KMOD_RGUI || sdl_mod & KMOD_LCTRL) { + // cmd/ctrl-q = quit + done = 1; + return; + } + break; + case SDLK_BACKSPACE: + projectM::deleteSearchText(); + break; + case SDLK_SLASH: + break; + case SDLK_BACKSLASH: + break; + case SDLK_RETURN: + if (!projectM::isTextInputActive()) { + SDL_StartTextInput(); + } + break; + case SDLK_ESCAPE: + if (projectM::isTextInputActive()) + SDL_StopTextInput(); + break; + case SDLK_i: + if (sdl_mod & KMOD_LGUI || sdl_mod & KMOD_RGUI || sdl_mod & KMOD_LCTRL) + { + toggleAudioInput(); + return; // handled + } + break; + case SDLK_s: + if (sdl_mod & KMOD_LGUI || sdl_mod & KMOD_RGUI || sdl_mod & KMOD_LCTRL) + { + // command-s: [s]tretch monitors + // Stereo requires fullscreen #if !STEREOSCOPIC_SBS - if (!this->stretch) { // if stretching is not already enabled, enable it. - stretchMonitors(); - this->stretch = true; - } else { - toggleFullScreen(); // else, just toggle full screen so we leave stretch mode. - this->stretch = false; - } + if (!this->stretch) { // if stretching is not already enabled, enable it. + stretchMonitors(); + this->stretch = true; + } else { + toggleFullScreen(); // else, just toggle full screen so we leave stretch mode. + this->stretch = false; + } #endif - return; // handled - } - case SDLK_m: - if (sdl_mod & KMOD_LGUI || sdl_mod & KMOD_RGUI || sdl_mod & KMOD_LCTRL) - { - // command-m: change [m]onitor - // Stereo requires fullscreen + return; // handled + } + case SDLK_m: + if (sdl_mod & KMOD_LGUI || sdl_mod & KMOD_RGUI || sdl_mod & KMOD_LCTRL) + { + // command-m: change [m]onitor + // Stereo requires fullscreen #if !STEREOSCOPIC_SBS - nextMonitor(); + nextMonitor(); #endif - this->stretch = false; // if we are switching monitors, ensure we disable monitor stretching. - return; // handled - } - case SDLK_f: - if (sdl_mod & KMOD_LGUI || sdl_mod & KMOD_RGUI || sdl_mod & KMOD_LCTRL) { - // command-f: fullscreen - // Stereo requires fullscreen + this->stretch = false; // if we are switching monitors, ensure we disable monitor stretching. + return; // handled + } + case SDLK_f: + if (sdl_mod & KMOD_LGUI || sdl_mod & KMOD_RGUI || sdl_mod & KMOD_LCTRL) { + // command-f: fullscreen + // Stereo requires fullscreen #if !STEREOSCOPIC_SBS - toggleFullScreen(); + toggleFullScreen(); #endif - this->stretch = false; // if we are toggling fullscreen, ensure we disable monitor stretching. - return; // handled + this->stretch = false; // if we are toggling fullscreen, ensure we disable monitor stretching. + return; // handled + } + break; + case SDLK_LEFT: + // selectPrevious(true); + break; + case SDLK_RIGHT: + // selectNext(true); + break; + case SDLK_UP: + break; + case SDLK_DOWN: + break; + + case SDLK_F3: + break; + + + case SDLK_SPACE: + if (!projectM::isTextInputActive(true)) + setPresetLock(!isPresetLocked()); + break; + case SDLK_F1: + break; + case SDLK_DELETE: + /* + try { + if (selectedPresetIndex(index)) { + DeleteFile( + LPCSTR( + getPresetURL(index).c_str() + ) + ); } - break; - case SDLK_LEFT: - // selectPrevious(true); - break; - case SDLK_RIGHT: - // selectNext(true); - break; - case SDLK_UP: - break; - case SDLK_DOWN: - break; - - case SDLK_F3: - break; - - - case SDLK_SPACE: - setPresetLock( - !isPresetLocked() - ); - break; - case SDLK_F1: - case SDLK_ESCAPE: - - // exit(1); - // show help with other keys - sdl_keycode = SDLK_F1; - break; - case SDLK_DELETE: - /* - try { - if (selectedPresetIndex(index)) { - DeleteFile( - LPCSTR( - getPresetURL(index).c_str() - ) - ); - } - } - catch (const std::exception & e) { - printf("Delete failed"); - } - */ - break; - case SDLK_RETURN: - /* - try { - if (selectedPresetIndex(index)) { - CopyFile( - LPCSTR( - app->getPresetURL(index).c_str() - ), - LPCTSTR(L"C:\\"), - false - ); - } - } - catch (const std::exception & e) { - printf("Delete failed"); - } - */ - break; + } + catch (const std::exception & e) { + printf("Delete failed"); + } + */ + break; } - // translate into projectM codes and perform default projectM handler evt = sdl2pmEvent(sdl_evt); mod = sdl2pmModifier(sdl_mod); @@ -514,6 +506,13 @@ void projectMSDL::pollEvent() { case SDL_MOUSEBUTTONUP: mouseDown = false; break; + case SDL_TEXTINPUT: + if (projectM::isTextInputActive(true)) + { + projectM::setSearchText(evt.text.text); + projectM::populatePresetMenu(); + } + break; case SDL_QUIT: done = true; break;