瀏覽代碼

Scroll to imported beatmap after import

kiwec 3 月之前
父節點
當前提交
dbb025b58f

+ 12 - 5
src/App/Osu/Database.cpp

@@ -8,6 +8,7 @@
 #include "BanchoNetworking.h"
 #include "Collections.h"
 #include "ConVar.h"
+#include "Database.h"
 #include "DatabaseBeatmap.h"
 #include "Engine.h"
 #include "File.h"
@@ -15,6 +16,7 @@
 #include "Osu.h"
 #include "Replay.h"
 #include "ResourceManager.h"
+#include "SongBrowser/SongBrowser.h"
 #include "Timer.h"
 #include "score.h"
 
@@ -362,12 +364,17 @@ void Database::save() {
     saveStars();
 }
 
-DatabaseBeatmap *Database::addBeatmap(std::string beatmapFolderPath) {
+BeatmapSet *Database::addBeatmapSet(std::string beatmapFolderPath) {
     BeatmapSet *beatmap = loadRawBeatmap(beatmapFolderPath);
-    if(beatmap != NULL) {
-        m_beatmapsets.push_back(beatmap);
-        m_neosu_sets.push_back(beatmap);
-    }
+    if(beatmap == NULL) return NULL;
+
+    m_beatmapsets.push_back(beatmap);
+    m_neosu_sets.push_back(beatmap);
+    osu->m_songBrowser2->addBeatmapSet(beatmap);
+
+    // XXX: Very slow
+    osu->m_songBrowser2->onSortChangeInt(convar->getConVarByName("osu_songbrowser_sortingtype")->getString(), false);
+
     return beatmap;
 }
 

+ 1 - 1
src/App/Osu/Database.h

@@ -66,7 +66,7 @@ class Database {
     void cancel();
     void save();
 
-    DatabaseBeatmap *addBeatmap(std::string beatmapFolderPath);
+    BeatmapSet *addBeatmapSet(std::string beatmapFolderPath);
 
     int addScore(MD5Hash beatmapMD5Hash, FinishedScore score);
     void deleteScore(MD5Hash beatmapMD5Hash, u64 scoreUnixTimestamp);

+ 2 - 6
src/App/Osu/Downloader.cpp

@@ -221,7 +221,6 @@ i32 get_beatmapset_id_from_osu_file(const u8* osu_data, size_t s_osu_data) {
     for(size_t i = 0; i < s_osu_data; i++) {
         if(osu_data[i] == '\n') {
             if(line.find("//") != 0) {
-                debugLog("%s\n", line.c_str());
                 sscanf(line.c_str(), " BeatmapSetID : %i \n", &set_id);
                 if(set_id != -1) return set_id;
             }
@@ -233,7 +232,6 @@ i32 get_beatmapset_id_from_osu_file(const u8* osu_data, size_t s_osu_data) {
     }
 
     if(line.find("//") != 0) {
-        debugLog("%s\n", line.c_str());
         sscanf(line.c_str(), " BeatmapSetID : %i \n", &set_id);
     }
 
@@ -399,8 +397,7 @@ DatabaseBeatmap* download_beatmap(i32 beatmap_id, MD5Hash beatmap_md5, float* pr
     if(*progress != 1.f) return NULL;
 
     auto mapset_path = UString::format(MCENGINE_DATA_DIR "maps/%d/", set_id);
-    osu->m_songBrowser2->getDatabase()->addBeatmap(mapset_path.toUtf8());
-    osu->m_songBrowser2->updateSongButtonSorting();
+    osu->m_songBrowser2->getDatabase()->addBeatmapSet(mapset_path.toUtf8());
     debugLog("Finished loading beatmapset %d.\n", set_id);
 
     beatmap = osu->getSongBrowser()->getDatabase()->getBeatmapDifficulty(beatmap_md5);
@@ -470,8 +467,7 @@ DatabaseBeatmap* download_beatmap(i32 beatmap_id, i32 beatmapset_id, float* prog
     if(*progress != 1.f) return NULL;
 
     auto mapset_path = UString::format(MCENGINE_DATA_DIR "maps/%d/", set_id);
-    osu->m_songBrowser2->getDatabase()->addBeatmap(mapset_path.toUtf8());
-    osu->m_songBrowser2->updateSongButtonSorting();
+    osu->m_songBrowser2->getDatabase()->addBeatmapSet(mapset_path.toUtf8());
     debugLog("Finished loading beatmapset %d.\n", set_id);
 
     beatmap = osu->getSongBrowser()->getDatabase()->getBeatmapDifficulty(beatmap_id);

+ 161 - 364
src/App/Osu/SongBrowser/SongBrowser.cpp

@@ -245,7 +245,7 @@ class NoRecordsSetElement : public CBaseUILabel {
     UString m_sIconString;
 };
 
-bool SongBrowser::SortByArtist::operator()(Button const *a, Button const *b) const {
+bool sort_by_artist(Button const *a, Button const *b) {
     if(a->getDatabaseBeatmap() == NULL || b->getDatabaseBeatmap() == NULL) return a->getSortHack() < b->getSortHack();
 
     int res = strcasecmp(a->getDatabaseBeatmap()->getArtist().c_str(), b->getDatabaseBeatmap()->getArtist().c_str());
@@ -253,7 +253,7 @@ bool SongBrowser::SortByArtist::operator()(Button const *a, Button const *b) con
     return res < 0;
 }
 
-bool SongBrowser::SortByBPM::operator()(Button const *a, Button const *b) const {
+bool sort_by_bpm(Button const *a, Button const *b) {
     if(a->getDatabaseBeatmap() == NULL || b->getDatabaseBeatmap() == NULL) return a->getSortHack() < b->getSortHack();
 
     int bpm1 = a->getDatabaseBeatmap()->getMostCommonBPM();
@@ -262,7 +262,7 @@ bool SongBrowser::SortByBPM::operator()(Button const *a, Button const *b) const
     return bpm1 < bpm2;
 }
 
-bool SongBrowser::SortByCreator::operator()(Button const *a, Button const *b) const {
+bool sort_by_creator(Button const *a, Button const *b) {
     if(a->getDatabaseBeatmap() == NULL || b->getDatabaseBeatmap() == NULL) return a->getSortHack() < b->getSortHack();
 
     int res = strcasecmp(a->getDatabaseBeatmap()->getCreator().c_str(), b->getDatabaseBeatmap()->getCreator().c_str());
@@ -270,7 +270,7 @@ bool SongBrowser::SortByCreator::operator()(Button const *a, Button const *b) co
     return res < 0;
 }
 
-bool SongBrowser::SortByDateAdded::operator()(Button const *a, Button const *b) const {
+bool sort_by_date_added(Button const *a, Button const *b) {
     if(a->getDatabaseBeatmap() == NULL || b->getDatabaseBeatmap() == NULL) return a->getSortHack() < b->getSortHack();
 
     long long time1 = a->getDatabaseBeatmap()->last_modification_time;
@@ -279,7 +279,7 @@ bool SongBrowser::SortByDateAdded::operator()(Button const *a, Button const *b)
     return time1 > time2;
 }
 
-bool SongBrowser::SortByDifficulty::operator()(Button const *a, Button const *b) const {
+bool sort_by_difficulty(Button const *a, Button const *b) {
     if(a->getDatabaseBeatmap() == NULL || b->getDatabaseBeatmap() == NULL) return a->getSortHack() < b->getSortHack();
 
     float stars1 = a->getDatabaseBeatmap()->getStarsNomod();
@@ -297,7 +297,7 @@ bool SongBrowser::SortByDifficulty::operator()(Button const *a, Button const *b)
     return a->getSortHack() < b->getSortHack();
 }
 
-bool SongBrowser::SortByLength::operator()(Button const *a, Button const *b) const {
+bool sort_by_length(Button const *a, Button const *b) {
     if(a->getDatabaseBeatmap() == NULL || b->getDatabaseBeatmap() == NULL) return a->getSortHack() < b->getSortHack();
 
     unsigned long length1 = a->getDatabaseBeatmap()->getLengthMS();
@@ -306,7 +306,7 @@ bool SongBrowser::SortByLength::operator()(Button const *a, Button const *b) con
     return length1 < length2;
 }
 
-bool SongBrowser::SortByTitle::operator()(Button const *a, Button const *b) const {
+bool sort_by_title(Button const *a, Button const *b) {
     if(a->getDatabaseBeatmap() == NULL || b->getDatabaseBeatmap() == NULL) return a->getSortHack() < b->getSortHack();
 
     int res = strcasecmp(a->getDatabaseBeatmap()->getTitle().c_str(), b->getDatabaseBeatmap()->getTitle().c_str());
@@ -330,17 +330,16 @@ SongBrowser::SongBrowser() : ScreenBackable() {
     }
 
     m_sortingMethod = SORT::SORT_ARTIST;
-    {
-        m_sortingMethods.push_back({SORT::SORT_ARTIST, "By Artist", new SortByArtist()});
-        m_sortingMethods.push_back({SORT::SORT_BPM, "By BPM", new SortByBPM()});
-        m_sortingMethods.push_back({SORT::SORT_CREATOR, "By Creator", new SortByCreator()});
-        m_sortingMethods.push_back({SORT::SORT_DATEADDED, "By Date Added", new SortByDateAdded()});
-        m_sortingMethods.push_back({SORT::SORT_DIFFICULTY, "By Difficulty", new SortByDifficulty()});
-        m_sortingMethods.push_back({SORT::SORT_LENGTH, "By Length", new SortByLength()});
-        /// m_sortingMethods.push_back({SORT::SORT_RANKACHIEVED, "By Rank Achieved", new SortByRankAchieved()}); // not
-        /// yet possible
-        m_sortingMethods.push_back({SORT::SORT_TITLE, "By Title", new SortByTitle()});
-    }
+    m_sortingComparator = sort_by_artist;
+
+    m_sortingMethods.push_back({SORT::SORT_ARTIST, "By Artist", sort_by_artist});
+    m_sortingMethods.push_back({SORT::SORT_BPM, "By BPM", sort_by_bpm});
+    m_sortingMethods.push_back({SORT::SORT_CREATOR, "By Creator", sort_by_creator});
+    m_sortingMethods.push_back({SORT::SORT_DATEADDED, "By Date Added", sort_by_date_added});
+    m_sortingMethods.push_back({SORT::SORT_DIFFICULTY, "By Difficulty", sort_by_difficulty});
+    m_sortingMethods.push_back({SORT::SORT_LENGTH, "By Length", sort_by_length});
+    m_sortingMethods.push_back({SORT::SORT_TITLE, "By Title", sort_by_title});
+    // m_sortingMethods.push_back({SORT::SORT_RANKACHIEVED, "By Rank Achieved", new SortByRankAchieved()}); // not yet possible
 
     // convar refs
     m_fps_max_ref = convar->getConVarByName("fps_max");
@@ -413,46 +412,29 @@ SongBrowser::SongBrowser() : ScreenBackable() {
     // build topbar right
     m_topbarRight = new CBaseUIContainer(0, 0, 0, 0, "");
 
-    addTopBarRightGroupButton("")->setVisible(false);  // NOTE: align with second tab
     {
         m_groupLabel = new CBaseUILabel(0, 0, 0, 0, "", "Group:");
         m_groupLabel->setSizeToContent(3);
         m_groupLabel->setDrawFrame(false);
         m_groupLabel->setDrawBackground(false);
-
         m_topbarRight->addBaseUIElement(m_groupLabel);
-    }
-    m_groupButton = addTopBarRightGroupButton("No Grouping");
-    m_groupButton->setClickCallback(fastdelegate::MakeDelegate(this, &SongBrowser::onGroupClicked));
 
-    {
-        // TODO: Add hover sounds
-
-        // "hardcoded" grouping tabs
-        m_collectionsButton = addTopBarRightTabButton("Collections");
-        m_collectionsButton->setClickCallback(fastdelegate::MakeDelegate(this, &SongBrowser::onGroupTabButtonClicked));
-        m_artistButton = addTopBarRightTabButton("By Artist");
-        m_artistButton->setClickCallback(fastdelegate::MakeDelegate(this, &SongBrowser::onGroupTabButtonClicked));
-        m_difficultiesButton = addTopBarRightTabButton("By Difficulty");
-        m_difficultiesButton->setClickCallback(fastdelegate::MakeDelegate(this, &SongBrowser::onGroupTabButtonClicked));
-        m_noGroupingButton = addTopBarRightTabButton("No Grouping");
-        m_noGroupingButton->setClickCallback(fastdelegate::MakeDelegate(this, &SongBrowser::onGroupTabButtonClicked));
-        m_noGroupingButton->setTextBrightColor(COLOR(255, 0, 255, 0));
-    }
-
-    addTopBarRightSortButton("")->setVisible(false);  // NOTE: align with last tab (1)
-    addTopBarRightSortButton("")->setVisible(false);  // NOTE: align with last tab (2)
-    addTopBarRightSortButton("")->setVisible(false);  // NOTE: align with last tab (3)
-    {
+        m_groupButton = new CBaseUIButton(0, 0, 0, 0, "", "No Grouping");
+        m_groupButton->setDrawBackground(false);
+        m_groupButton->setClickCallback(fastdelegate::MakeDelegate(this, &SongBrowser::onGroupClicked));
+        m_topbarRight->addBaseUIElement(m_groupButton);
+
         m_sortLabel = new CBaseUILabel(0, 0, 0, 0, "", "Sort:");
         m_sortLabel->setSizeToContent(3);
         m_sortLabel->setDrawFrame(false);
         m_sortLabel->setDrawBackground(false);
-
         m_topbarRight->addBaseUIElement(m_sortLabel);
+
+        m_sortButton = new CBaseUIButton(0, 0, 0, 0, "", "By Date Added");
+        m_sortButton->setDrawBackground(false);
+        m_sortButton->setClickCallback(fastdelegate::MakeDelegate(this, &SongBrowser::onSortClicked));
+        m_topbarRight->addBaseUIElement(m_sortButton);
     }
-    m_sortButton = addTopBarRightSortButton("By Date Added");
-    m_sortButton->setClickCallback(fastdelegate::MakeDelegate(this, &SongBrowser::onSortClicked));
 
     // context menu
     m_contextMenu = new UIContextMenu(50, 50, 150, 0, "");
@@ -522,8 +504,6 @@ SongBrowser::SongBrowser() : ScreenBackable() {
     m_bInSearch = (osu_songbrowser_search_hardcoded_filter.getString().length() > 0);
     m_searchPrevGroup = GROUP::GROUP_NO_GROUPING;
     m_backgroundSearchMatcher = new SongBrowserBackgroundSearchMatcher();
-    m_bOnAfterSortingOrGroupChangeUpdateScheduled = false;
-    m_bOnAfterSortingOrGroupChangeUpdateScheduledAutoScroll = false;
 
     updateLayout();
 }
@@ -570,10 +550,6 @@ SongBrowser::~SongBrowser() {
     SAFE_DELETE(m_scoreBrowserScoresStillLoadingElement);
     SAFE_DELETE(m_scoreBrowserNoRecordsYetElement);
 
-    for(size_t i = 0; i < m_sortingMethods.size(); i++) {
-        delete m_sortingMethods[i].comparator;
-    }
-
     SAFE_DELETE(m_selectedBeatmap);
     SAFE_DELETE(m_search);
     SAFE_DELETE(m_topbarLeft);
@@ -900,8 +876,7 @@ bool SongBrowser::selectBeatmapset(i32 set_id) {
     if(beatmapset == NULL) {
         // Pasted from Downloader::download_beatmap
         auto mapset_path = UString::format(MCENGINE_DATA_DIR "maps/%d/", set_id);
-        osu->m_songBrowser2->getDatabase()->addBeatmap(mapset_path.toUtf8());
-        osu->m_songBrowser2->updateSongButtonSorting();
+        getDatabase()->addBeatmapSet(mapset_path.toUtf8());
         debugLog("Finished loading beatmapset %d.\n", set_id);
 
         beatmapset = getDatabase()->getBeatmapSet(set_id);
@@ -925,8 +900,9 @@ bool SongBrowser::selectBeatmapset(i32 set_id) {
         osu->getNotificationOverlay()->addNotification("Beatmapset has no difficulties :/");
         return false;
     } else {
-        osu->m_songBrowser2->onDifficultySelected(best_diff, false);
-        osu->m_songBrowser2->selectSelectedBeatmapSongButton();
+        onSelectionChange(hashToSongButton[best_diff->getMD5Hash()], false);
+        onDifficultySelected(best_diff, false);
+        selectSelectedBeatmapSongButton();
         return true;
     }
 }
@@ -1064,8 +1040,9 @@ void SongBrowser::mouse_update(bool *propagate_clicks) {
     // if cursor is to the left edge of the screen, force center currently selected beatmap/diff
     // but only if the context menu is currently not visible (since we don't want move things while e.g. managing
     // collections etc.)
-    if(engine->getMouse()->getPos().x < osu->getScreenWidth() * 0.1f && !m_contextMenu->isVisible())
-        scrollToSelectedSongButton();
+    if(engine->getMouse()->getPos().x < osu->getScreenWidth() * 0.1f && !m_contextMenu->isVisible()) {
+        scrollToSongButton(m_selectedButton);
+    }
 
     // handle searching
     if(m_fSearchWaitTime != 0.0f && engine->getTime() > m_fSearchWaitTime) {
@@ -1083,9 +1060,11 @@ void SongBrowser::mouse_update(bool *propagate_clicks) {
         }
 
         if(m_backgroundSearchMatcher->isDead()) {
-            if(m_bOnAfterSortingOrGroupChangeUpdateScheduled) {
-                m_bOnAfterSortingOrGroupChangeUpdateScheduled = false;
-                onAfterSortingOrGroupChangeUpdateInt(m_bOnAfterSortingOrGroupChangeUpdateScheduledAutoScroll);
+            if(m_scheduled_scroll_to_selected_button) {
+                m_scheduled_scroll_to_selected_button = false;
+
+                // TODO @kiwec: This doesn't scroll if we switch between groupings
+                scrollToSongButton(m_selectedButton);
             }
         }
     }
@@ -1432,25 +1411,17 @@ CBaseUIContainer *SongBrowser::setVisible(bool visible) {
 void SongBrowser::selectSelectedBeatmapSongButton() {
     if(m_selectedBeatmap == NULL) return;
 
-    const std::vector<CBaseUIElement *> &elements = m_songBrowser->getContainer()->getElements();
-    for(auto elm : elements) {
-        SongButton *btn = dynamic_cast<SongButton *>(elm);
-        if(btn == NULL) continue;
-        if(btn->getDatabaseBeatmap() == NULL) continue;
-
-        std::vector<DatabaseBeatmap *> diffs = btn->getDatabaseBeatmap()->getDifficulties();
-        diffs.push_back(btn->getDatabaseBeatmap());
-
-        for(auto diff : diffs) {
-            if(m_selectedBeatmap->getSelectedDifficulty2() == diff) {
-                btn->deselect();  // if we select() it when already selected, it would start playing!
-                btn->select();
-                return;
-            }
-        }
+	auto diff = m_selectedBeatmap->getSelectedDifficulty2();
+    if(diff == NULL) return;
+
+    auto it = hashToSongButton.find(diff->getMD5Hash());
+    if(it == hashToSongButton.end()) {
+        debugLog("No song button found for currently selected beatmap...\n");
+        return;
     }
 
-    debugLog("No song button found for currently selected beatmap...\n");
+    it->second->deselect();  // if we select() it when already selected, it would start playing!
+    it->second->select();
 }
 
 void SongBrowser::onPlayEnd(bool quit) {
@@ -1460,8 +1431,7 @@ void SongBrowser::onPlayEnd(bool quit) {
     if(!quit) {
         rebuildScoreButtons();
 
-        SongDifficultyButton *selectedSongDiffButton =
-            dynamic_cast<SongDifficultyButton *>(findCurrentlySelectedSongButton());
+        SongDifficultyButton *selectedSongDiffButton = dynamic_cast<SongDifficultyButton *>(m_selectedButton);
         if(selectedSongDiffButton != NULL) selectedSongDiffButton->updateGrade();
     }
 
@@ -1471,6 +1441,7 @@ void SongBrowser::onPlayEnd(bool quit) {
 }
 
 void SongBrowser::onSelectionChange(Button *button, bool rebuild) {
+    m_selectedButton = button;
     if(button == NULL) return;
 
     m_contextMenu->setVisible2(false);
@@ -1483,9 +1454,6 @@ void SongBrowser::onSelectionChange(Button *button, bool rebuild) {
     SongDifficultyButton *songDiffButtonPointer = dynamic_cast<SongDifficultyButton *>(button);
     CollectionButton *collectionButtonPointer = dynamic_cast<CollectionButton *>(button);
 
-    /// debugLog("onSelectionChange(%i, %i, %i)\n", (int)(songButtonPointer != NULL), (int)(songDiffButtonPointer !=
-    /// NULL), (int)(collectionButtonPointer != NULL));
-
     if(songDiffButtonPointer != NULL) {
         if(m_selectionPreviousSongDiffButton != NULL && m_selectionPreviousSongDiffButton != songDiffButtonPointer)
             m_selectionPreviousSongDiffButton->deselect();
@@ -1682,20 +1650,20 @@ void SongBrowser::refreshBeatmaps() {
     m_db->load();
 }
 
-void SongBrowser::addBeatmap(DatabaseBeatmap *beatmap) {
-    if(beatmap->getDifficulties().size() < 1) return;
+void SongBrowser::addBeatmapSet(BeatmapSet *mapset) {
+    if(mapset->getDifficulties().size() < 1) return;
 
     SongButton *songButton;
-    if(beatmap->getDifficulties().size() > 1) {
+    if(mapset->getDifficulties().size() > 1) {
         songButton =
-            new SongButton(this, m_songBrowser, m_contextMenu, 250, 250 + m_beatmaps.size() * 50, 200, 50, "", beatmap);
+            new SongButton(this, m_songBrowser, m_contextMenu, 250, 250 + m_beatmaps.size() * 50, 200, 50, "", mapset);
     } else {
         songButton = new SongDifficultyButton(this, m_songBrowser, m_contextMenu, 250, 250 + m_beatmaps.size() * 50,
-                                              200, 50, "", beatmap->getDifficulties()[0], NULL);
+                                              200, 50, "", mapset->getDifficulties()[0], NULL);
     }
 
     m_songButtons.push_back(songButton);
-    for(auto diff : beatmap->getDifficulties()) {
+    for(auto diff : mapset->getDifficulties()) {
         hashToSongButton[diff->getMD5Hash()] = songButton;
     }
 
@@ -1706,38 +1674,24 @@ void SongBrowser::addBeatmap(DatabaseBeatmap *beatmap) {
             for(Button *child : songButton->getChildren()) {
                 tempChildrenForGroups.push_back(child);
             }
-        } else
+        } else {
             tempChildrenForGroups.push_back(songButton);
+        }
     }
 
-    // add beatmap to all necessary groups
+    // add mapset to all necessary groups
     {
-        // artist
-        if(m_artistCollectionButtons.size() == 28) {
-            const std::string &artist = beatmap->getArtist();
-            if(artist.length() > 0) {
-                const char firstChar = artist[0];
-
-                const bool isNumber = (firstChar >= '0' && firstChar <= '9');
-                const bool isLowerCase = (firstChar >= 'a' && firstChar <= 'z');
-                const bool isUpperCase = (firstChar >= 'A' && firstChar <= 'Z');
-
-                if(isNumber)
-                    m_artistCollectionButtons[0]->getChildren().push_back(songButton);
-                else if(isLowerCase || isUpperCase) {
-                    const int index = 1 + (25 - (isLowerCase ? 'z' - firstChar : 'Z' - firstChar));
-                    if(index > 0 && index < 27) m_artistCollectionButtons[index]->getChildren().push_back(songButton);
-                } else
-                    m_artistCollectionButtons[27]->getChildren().push_back(songButton);
-            }
-        }
+        addSongButtonToAlphanumericGroup(songButton, m_artistCollectionButtons, mapset->getArtist());
+        addSongButtonToAlphanumericGroup(songButton, m_creatorCollectionButtons, mapset->getCreator());
+        addSongButtonToAlphanumericGroup(songButton, m_titleCollectionButtons, mapset->getTitle());
 
         // difficulty
         if(m_difficultyCollectionButtons.size() == 12) {
-            for(size_t i = 0; i < tempChildrenForGroups.size(); i++) {
-                const int index =
-                    clamp<int>((int)tempChildrenForGroups[i]->getDatabaseBeatmap()->getStarsNomod(), 0, 11);
-                m_difficultyCollectionButtons[index]->getChildren().push_back(tempChildrenForGroups[i]);
+            for(auto diff_btn : tempChildrenForGroups) {
+                const int index = clamp<int>((int)diff_btn->getDatabaseBeatmap()->getStarsNomod(), 0, 11);
+                auto children = &m_difficultyCollectionButtons[index]->getChildren();
+                auto it = std::lower_bound(children->begin(), children->end(), diff_btn, sort_by_difficulty);
+                children->insert(it, diff_btn);
             }
         }
 
@@ -1747,26 +1701,6 @@ void SongBrowser::addBeatmap(DatabaseBeatmap *beatmap) {
             // TODO: have to rip apart children and group separately depending on bpm, ffs
         }
 
-        // creator
-        if(m_creatorCollectionButtons.size() == 28) {
-            const std::string &creator = beatmap->getCreator();
-            if(creator.length() > 0) {
-                const char firstChar = creator[0];
-
-                const bool isNumber = (firstChar >= '0' && firstChar <= '9');
-                const bool isLowerCase = (firstChar >= 'a' && firstChar <= 'z');
-                const bool isUpperCase = (firstChar >= 'A' && firstChar <= 'Z');
-
-                if(isNumber)
-                    m_creatorCollectionButtons[0]->getChildren().push_back(songButton);
-                else if(isLowerCase || isUpperCase) {
-                    const int index = 1 + (25 - (isLowerCase ? 'z' - firstChar : 'Z' - firstChar));
-                    if(index > 0 && index < 27) m_creatorCollectionButtons[index]->getChildren().push_back(songButton);
-                } else
-                    m_creatorCollectionButtons[27]->getChildren().push_back(songButton);
-            }
-        }
-
         // dateadded
         {
             // TODO: extremely annoying
@@ -1774,47 +1708,59 @@ void SongBrowser::addBeatmap(DatabaseBeatmap *beatmap) {
 
         // length
         if(m_lengthCollectionButtons.size() == 7) {
-            for(size_t i = 0; i < tempChildrenForGroups.size(); i++) {
-                const unsigned long lengthMS = tempChildrenForGroups[i]->getDatabaseBeatmap()->getLengthMS();
-                if(lengthMS <= 1000 * 60)
-                    m_lengthCollectionButtons[0]->getChildren().push_back(tempChildrenForGroups[i]);
-                else if(lengthMS <= 1000 * 60 * 2)
-                    m_lengthCollectionButtons[1]->getChildren().push_back(tempChildrenForGroups[i]);
-                else if(lengthMS <= 1000 * 60 * 3)
-                    m_lengthCollectionButtons[2]->getChildren().push_back(tempChildrenForGroups[i]);
-                else if(lengthMS <= 1000 * 60 * 4)
-                    m_lengthCollectionButtons[3]->getChildren().push_back(tempChildrenForGroups[i]);
-                else if(lengthMS <= 1000 * 60 * 5)
-                    m_lengthCollectionButtons[4]->getChildren().push_back(tempChildrenForGroups[i]);
-                else if(lengthMS <= 1000 * 60 * 10)
-                    m_lengthCollectionButtons[5]->getChildren().push_back(tempChildrenForGroups[i]);
-                else
-                    m_lengthCollectionButtons[6]->getChildren().push_back(tempChildrenForGroups[i]);
-            }
-        }
+            for(auto diff_btn : tempChildrenForGroups) {
+                const u32 lengthMS = diff_btn->getDatabaseBeatmap()->getLengthMS();
+
+                std::vector<Button *> *children = NULL;
+                if(lengthMS <= 1000 * 60) {
+                    children = &m_lengthCollectionButtons[0]->getChildren();
+                } else if(lengthMS <= 1000 * 60 * 2) {
+                    children = &m_lengthCollectionButtons[1]->getChildren();
+                } else if(lengthMS <= 1000 * 60 * 3) {
+                    children = &m_lengthCollectionButtons[2]->getChildren();
+                } else if(lengthMS <= 1000 * 60 * 4) {
+                    children = &m_lengthCollectionButtons[3]->getChildren();
+                } else if(lengthMS <= 1000 * 60 * 5) {
+                    children = &m_lengthCollectionButtons[4]->getChildren();
+                } else if(lengthMS <= 1000 * 60 * 10) {
+                    children = &m_lengthCollectionButtons[5]->getChildren();
+                } else {
+                    children = &m_lengthCollectionButtons[6]->getChildren();
+                }
 
-        // title
-        if(m_titleCollectionButtons.size() == 28) {
-            const std::string &creator = beatmap->getTitle();
-            if(creator.length() > 0) {
-                const char firstChar = creator[0];
-
-                const bool isNumber = (firstChar >= '0' && firstChar <= '9');
-                const bool isLowerCase = (firstChar >= 'a' && firstChar <= 'z');
-                const bool isUpperCase = (firstChar >= 'A' && firstChar <= 'Z');
-
-                if(isNumber)
-                    m_titleCollectionButtons[0]->getChildren().push_back(songButton);
-                else if(isLowerCase || isUpperCase) {
-                    const int index = 1 + (25 - (isLowerCase ? 'z' - firstChar : 'Z' - firstChar));
-                    if(index > 0 && index < 27) m_titleCollectionButtons[index]->getChildren().push_back(songButton);
-                } else
-                    m_titleCollectionButtons[27]->getChildren().push_back(songButton);
+                auto it = std::lower_bound(children->begin(), children->end(), songButton, sort_by_length);
+                children->insert(it, diff_btn);
             }
         }
     }
 }
 
+void SongBrowser::addSongButtonToAlphanumericGroup(SongButton *btn, std::vector<CollectionButton *> &group, const std::string &name) {
+    if(group.size() != 28) {
+        debugLog("Alphanumeric group wasn't initialized!\n");
+        return;
+    }
+
+    const char firstChar = name.length() == 0 ? '#' : name[0];
+    const bool isNumber = (firstChar >= '0' && firstChar <= '9');
+    const bool isLowerCase = (firstChar >= 'a' && firstChar <= 'z');
+    const bool isUpperCase = (firstChar >= 'A' && firstChar <= 'Z');
+
+    std::vector<Button *> *children = NULL;
+    if(isNumber) {
+        children = &group[0]->getChildren();
+    } else if(isLowerCase || isUpperCase) {
+        const int index = 1 + (25 - (isLowerCase ? 'z' - firstChar : 'Z' - firstChar));
+        children = &group[index]->getChildren();
+    } else {
+        children = &group[27]->getChildren();
+    }
+
+    auto it = std::lower_bound(children->begin(), children->end(), btn, m_sortingComparator);
+    if(Osu::debug->getBool()) debugLog("Inserting %s at index %d\n", name.c_str(), it - children->begin());
+    children->insert(it, btn);
+}
+
 void SongBrowser::requestNextScrollToSongButtonJumpFix(SongDifficultyButton *diffButton) {
     if(diffButton == NULL) return;
 
@@ -1826,7 +1772,11 @@ void SongBrowser::requestNextScrollToSongButtonJumpFix(SongDifficultyButton *dif
 }
 
 void SongBrowser::scrollToSongButton(Button *songButton, bool alignOnTop) {
-    if(songButton == NULL) return;
+    if(songButton == NULL) {
+        debugLog("scrollToSongButton(): songButton == NULL\n");
+        m_songBrowser->scrollToTop();
+        return;
+    }
 
     // NOTE: compensate potential scroll jump due to added/removed elements (feels a lot better this way, also easier on
     // the eyes)
@@ -1849,22 +1799,6 @@ void SongBrowser::scrollToSongButton(Button *songButton, bool alignOnTop) {
                              (alignOnTop ? (0) : (m_songBrowser->getSize().y / 2 - songButton->getSize().y / 2)));
 }
 
-Button *SongBrowser::findCurrentlySelectedSongButton() const {
-    Button *selectedButton = NULL;
-    const std::vector<CBaseUIElement *> &elements = m_songBrowser->getContainer()->getElements();
-    for(size_t i = 0; i < elements.size(); i++) {
-        Button *button = dynamic_cast<Button *>(elements[i]);
-        if(button != NULL && button->isSelected())  // NOTE: fall through multiple selected buttons (e.g. collections)
-            selectedButton = button;
-    }
-    return selectedButton;
-}
-
-void SongBrowser::scrollToSelectedSongButton() {
-    auto selectedButton = findCurrentlySelectedSongButton();
-    scrollToSongButton(selectedButton);
-}
-
 void SongBrowser::rebuildSongButtons() {
     m_songBrowser->getContainer()->empty();
 
@@ -1922,11 +1856,11 @@ void SongBrowser::rebuildSongButtons() {
 
     // TODO: regroup diffs which are next to each other into one song button (parent button)
     // TODO: regrouping is non-deterministic, depending on the searching method used.
-    // TODO: meaning that any number of "clusters" of diffs belonging to the same beatmap could build, requiring
-    // multiple song "parent" buttons for the same beatmap (if touching group size >= 2)
-    // TODO: when regrouping, these "fake" parent buttons have to be deleted on every reload. this means that the
-    // selection state logic has to be kept cleared of any invalid pointers!
-    // TODO: (including everything else which would rely on having a permanent pointer to an SongButton)
+    //       meaning that any number of "clusters" of diffs belonging to the same beatmap could build, requiring
+    //       multiple song "parent" buttons for the same beatmap (if touching group size >= 2)
+    //       when regrouping, these "fake" parent buttons have to be deleted on every reload. this means that the
+    //       selection state logic has to be kept cleared of any invalid pointers!
+    //       (including everything else which would rely on having a permanent pointer to an SongButton)
 
     updateSongButtonLayout();
 }
@@ -1980,8 +1914,6 @@ void SongBrowser::updateSongButtonLayout() {
     m_songBrowser->setScrollSizeToContent(m_songBrowser->getSize().y / 2);
 }
 
-void SongBrowser::updateSongButtonSorting() { onSortChange(osu_songbrowser_scores_sortingtype.getString()); }
-
 bool SongBrowser::searchMatcher(const DatabaseBeatmap *databaseBeatmap,
                                 const std::vector<UString> &searchStringTokens) {
     if(databaseBeatmap == NULL) return false;
@@ -2350,66 +2282,26 @@ void SongBrowser::updateLayout() {
                            osu->getSkin()->getSongSelectTop()->getHeight() * m_fSongSelectTopScale *
                                osu_songbrowser_topbar_right_height_percent.getFloat());
 
-    const int topbarRightTabButtonMargin = 10 * dpiScale;
-    const int topbarRightTabButtonHeight = 30 * dpiScale;
-    const int topbarRightTabButtonWidth = clamp<float>(
-        (float)(m_topbarRight->getSize().x - 2 * topbarRightTabButtonMargin) / (float)m_topbarRightTabButtons.size(),
-        0.0f, 200.0f * dpiScale);
-    for(int i = 0; i < m_topbarRightTabButtons.size(); i++) {
-        m_topbarRightTabButtons[i]->onResized();  // HACKHACK: framework bug (should update string metrics on setSize())
-        m_topbarRightTabButtons[i]->setSize(topbarRightTabButtonWidth, topbarRightTabButtonHeight);
-        m_topbarRightTabButtons[i]->setScrollPos(
-            m_topbarRight->getSize().x -
-                (topbarRightTabButtonMargin + (m_topbarRightTabButtons.size() - i) * topbarRightTabButtonWidth),
-            m_topbarRight->getSize().y - m_topbarRightTabButtons[i]->getSize().y);
-    }
-
-    if(m_topbarRightTabButtons.size() > 0) {
-        m_groupLabel->onResized();  // HACKHACK: framework bug (should update string metrics on setSizeToContent())
-        m_groupLabel->setSizeToContent(3 * dpiScale);
-        m_groupLabel->setScrollPos(m_topbarRightTabButtons[0]->getRelPos() +
-                                Vector2(-m_groupLabel->getSize().x, m_topbarRightTabButtons[0]->getSize().y / 2.0f -
-                                                                        m_groupLabel->getSize().y / 2.0f));
-    }
-
-    const int topbarRightSortButtonMargin = 10 * dpiScale;
-    const int topbarRightSortButtonHeight = 30 * dpiScale;
-    const int topbarRightSortButtonWidth = clamp<float>(
-        (float)(m_topbarRight->getSize().x - 2 * topbarRightSortButtonMargin) / (float)m_topbarRightSortButtons.size(),
-        0.0f, 200.0f * dpiScale);
-    for(int i = 0; i < m_topbarRightSortButtons.size(); i++) {
-        m_topbarRightSortButtons[i]->setSize(topbarRightSortButtonWidth, topbarRightSortButtonHeight);
-        m_topbarRightSortButtons[i]->setScrollPos(
-            m_topbarRight->getSize().x -
-                (topbarRightSortButtonMargin + (m_topbarRightTabButtons.size() - i) * topbarRightSortButtonWidth),
-            topbarRightSortButtonMargin);
-    }
-    for(int i = 0; i < m_topbarRightGroupButtons.size(); i++) {
-        m_topbarRightGroupButtons[i]->setSize(topbarRightSortButtonWidth, topbarRightSortButtonHeight);
-        m_topbarRightGroupButtons[i]->setScrollPos(
-            m_topbarRight->getSize().x -
-                (topbarRightSortButtonMargin + (m_topbarRightTabButtons.size() - i) * topbarRightSortButtonWidth),
-            topbarRightSortButtonMargin);
-    }
-
-    if(m_topbarRightGroupButtons.size() > 0) {
-        m_groupLabel->onResized();  // HACKHACK: framework bug (should update string metrics on setSizeToContent())
-        m_groupLabel->setSizeToContent(3 * dpiScale);
-        m_groupLabel->setScrollPos(
-            m_topbarRightGroupButtons[m_topbarRightGroupButtons.size() - 1]->getRelPos() +
-            Vector2(-m_groupLabel->getSize().x,
-                    m_topbarRightGroupButtons[m_topbarRightGroupButtons.size() - 1]->getSize().y / 2.0f -
-                        m_groupLabel->getSize().y / 2.0f));
-    }
-    if(m_topbarRightSortButtons.size() > 0) {
-        m_sortLabel->onResized();  // HACKHACK: framework bug (should update string metrics on setSizeToContent())
-        m_sortLabel->setSizeToContent(3 * dpiScale);
-        m_sortLabel->setScrollPos(
-            m_topbarRightSortButtons[m_topbarRightSortButtons.size() - 1]->getRelPos() +
-            Vector2(-m_sortLabel->getSize().x,
-                    m_topbarRightSortButtons[m_topbarRightSortButtons.size() - 1]->getSize().y / 2.0f -
-                        m_sortLabel->getSize().y / 2.0f));
-    }
+    float btn_margin = 10.f * dpiScale;
+    m_sortButton->setSize(200.f * dpiScale, 30.f * dpiScale);
+    m_sortButton->setScrollPos(m_topbarRight->getSize().x - (m_sortButton->getSize().x + btn_margin), btn_margin);
+
+    m_sortLabel->onResized();  // HACKHACK: framework bug (should update string metrics on setSizeToContent())
+    m_sortLabel->setSizeToContent(3 * dpiScale);
+    m_sortLabel->setScrollPos(
+        m_sortButton->getRelPos().x - (m_sortLabel->getSize().x + btn_margin),
+        (m_sortLabel->getSize().y + btn_margin) / 2.f
+    );
+
+    m_groupButton->setSize(m_sortButton->getSize());
+    m_groupButton->setScrollPos(m_sortLabel->getRelPos().x - (m_sortButton->getSize().x + 30.f * dpiScale + btn_margin), btn_margin);
+
+    m_groupLabel->onResized();  // HACKHACK: framework bug (should update string metrics on setSizeToContent())
+    m_groupLabel->setSizeToContent(3 * dpiScale);
+    m_groupLabel->setScrollPos(
+        m_groupButton->getRelPos().x - (m_groupLabel->getSize().x + btn_margin),
+        (m_groupLabel->getSize().y + btn_margin) / 2.f
+    );
 
     m_topbarRight->update_pos();
 
@@ -2692,45 +2584,6 @@ UISelectionButton *SongBrowser::addBottombarNavButton(std::function<SkinImage *(
     return btn;
 }
 
-CBaseUIButton *SongBrowser::addTopBarRightTabButton(UString text) {
-    // sanity check
-    {
-        bool isValid = false;
-        for(size_t i = 0; i < m_groupings.size(); i++) {
-            if(m_groupings[i].name == text) {
-                isValid = true;
-                break;
-            }
-        }
-
-        if(!isValid)
-            engine->showMessageError("Error",
-                                     UString::format("Invalid grouping name for tab button \"%s\"!", text.toUtf8()));
-    }
-
-    CBaseUIButton *btn = new CBaseUIButton(0, 0, 0, 0, "", text);
-    btn->setDrawBackground(false);
-    m_topbarRight->addBaseUIElement(btn);
-    m_topbarRightTabButtons.push_back(btn);
-    return btn;
-}
-
-CBaseUIButton *SongBrowser::addTopBarRightGroupButton(UString text) {
-    CBaseUIButton *btn = new CBaseUIButton(0, 0, 0, 0, "", text);
-    btn->setDrawBackground(false);
-    m_topbarRight->addBaseUIElement(btn);
-    m_topbarRightGroupButtons.push_back(btn);
-    return btn;
-}
-
-CBaseUIButton *SongBrowser::addTopBarRightSortButton(UString text) {
-    CBaseUIButton *btn = new CBaseUIButton(0, 0, 0, 0, "", text);
-    btn->setDrawBackground(false);
-    m_topbarRight->addBaseUIElement(btn);
-    m_topbarRightSortButtons.push_back(btn);
-    return btn;
-}
-
 CBaseUIButton *SongBrowser::addTopBarLeftTabButton(UString text) {
     CBaseUIButton *btn = new CBaseUIButton(0, 0, 0, 0, "", text);
     btn->setDrawBackground(false);
@@ -2901,7 +2754,7 @@ void SongBrowser::onDatabaseLoadingFinished() {
 
     // add all beatmaps (build buttons)
     for(size_t i = 0; i < m_beatmaps.size(); i++) {
-        addBeatmap(m_beatmaps[i]);
+        addBeatmapSet(m_beatmaps[i]);
     }
 
     // build collections
@@ -3172,19 +3025,6 @@ void SongBrowser::onGroupChange(UString text, int id) {
     // update group combobox button text
     m_groupButton->setText(grouping->name);
 
-    // highlight current tab (if any tab button exists for group)
-    bool hasTabButton = false;
-    for(size_t i = 0; i < m_topbarRightTabButtons.size(); i++) {
-        if(m_topbarRightTabButtons[i]->getText() == grouping->name) {
-            hasTabButton = true;
-            m_topbarRightTabButtons[i]->setTextBrightColor(highlightColor);
-        } else
-            m_topbarRightTabButtons[i]->setTextBrightColor(defaultColor);
-    }
-
-    // if there is no tab button for this group, then highlight combobox button instead
-    m_groupButton->setTextBrightColor(hasTabButton ? defaultColor : highlightColor);
-
     // and update the actual songbrowser contents
     switch(grouping->type) {
         case GROUP::GROUP_NO_GROUPING:
@@ -3229,43 +3069,27 @@ void SongBrowser::onSortClicked(CBaseUIButton *button) {
     }
     m_contextMenu->end(false, false);
     m_contextMenu->setClickCallback(fastdelegate::MakeDelegate(this, &SongBrowser::onSortChange));
-
-    // NOTE: don't remember group setting on shutdown
-
-    // manual hack for small resolutions
-    if(m_contextMenu->getRelPos().x + m_contextMenu->getSize().x > m_topbarRight->getSize().x) {
-        int newRelPosX = m_topbarRight->getSize().x - m_contextMenu->getSize().x - 1;
-        m_contextMenu->setScrollPosX(newRelPosX);
-        m_contextMenu->setPosX(m_topbarRight->getPos().x + m_topbarRight->getSize().x - m_contextMenu->getSize().x - 1);
-    }
 }
 
 void SongBrowser::onSortChange(UString text, int id) { onSortChangeInt(text, true); }
 
 void SongBrowser::onSortChangeInt(UString text, bool autoScroll) {
-    SORTING_METHOD *sortingMethod = (m_sortingMethods.size() > 3 ? &m_sortingMethods[3] : NULL);
+    SORTING_METHOD *sortingMethod = &m_sortingMethods[3];
     for(size_t i = 0; i < m_sortingMethods.size(); i++) {
         if(m_sortingMethods[i].name == text) {
             sortingMethod = &m_sortingMethods[i];
             break;
         }
     }
-    if(sortingMethod == NULL) return;
 
     m_sortingMethod = sortingMethod->type;
+    m_sortingComparator = sortingMethod->comparator;
     m_sortButton->setText(sortingMethod->name);
 
     osu_songbrowser_sortingtype.setValue(sortingMethod->name);  // NOTE: remember persistently
 
-    struct COMPARATOR_WRAPPER {
-        SORTING_COMPARATOR *comp;
-        bool operator()(Button const *a, Button const *b) const { return comp->operator()(a, b); }
-    };
-    COMPARATOR_WRAPPER comparatorWrapper;
-    comparatorWrapper.comp = sortingMethod->comparator;
-
     // resort primitive master button array (all songbuttons, No Grouping)
-    std::sort(m_songButtons.begin(), m_songButtons.end(), comparatorWrapper);
+    std::sort(m_songButtons.begin(), m_songButtons.end(), m_sortingComparator);
 
     // resort Collection buttons (one button for each collection)
     // these are always sorted alphabetically by name
@@ -3278,44 +3102,44 @@ void SongBrowser::onSortChangeInt(UString text, bool autoScroll) {
     // resort Collection button array (each group of songbuttons inside each Collection)
     for(size_t i = 0; i < m_collectionButtons.size(); i++) {
         std::vector<Button *> &children = m_collectionButtons[i]->getChildren();
-        std::sort(children.begin(), children.end(), comparatorWrapper);
+        std::sort(children.begin(), children.end(), m_sortingComparator);
         m_collectionButtons[i]->setChildren(children);
     }
 
     // etc.
     for(size_t i = 0; i < m_artistCollectionButtons.size(); i++) {
         std::vector<Button *> &children = m_artistCollectionButtons[i]->getChildren();
-        std::sort(children.begin(), children.end(), comparatorWrapper);
+        std::sort(children.begin(), children.end(), m_sortingComparator);
         m_artistCollectionButtons[i]->setChildren(children);
     }
     for(size_t i = 0; i < m_difficultyCollectionButtons.size(); i++) {
         std::vector<Button *> &children = m_difficultyCollectionButtons[i]->getChildren();
-        std::sort(children.begin(), children.end(), comparatorWrapper);
+        std::sort(children.begin(), children.end(), m_sortingComparator);
         m_difficultyCollectionButtons[i]->setChildren(children);
     }
     for(size_t i = 0; i < m_bpmCollectionButtons.size(); i++) {
         std::vector<Button *> &children = m_bpmCollectionButtons[i]->getChildren();
-        std::sort(children.begin(), children.end(), comparatorWrapper);
+        std::sort(children.begin(), children.end(), m_sortingComparator);
         m_bpmCollectionButtons[i]->setChildren(children);
     }
     for(size_t i = 0; i < m_creatorCollectionButtons.size(); i++) {
         std::vector<Button *> &children = m_creatorCollectionButtons[i]->getChildren();
-        std::sort(children.begin(), children.end(), comparatorWrapper);
+        std::sort(children.begin(), children.end(), m_sortingComparator);
         m_creatorCollectionButtons[i]->setChildren(children);
     }
     for(size_t i = 0; i < m_dateaddedCollectionButtons.size(); i++) {
         std::vector<Button *> &children = m_dateaddedCollectionButtons[i]->getChildren();
-        std::sort(children.begin(), children.end(), comparatorWrapper);
+        std::sort(children.begin(), children.end(), m_sortingComparator);
         m_dateaddedCollectionButtons[i]->setChildren(children);
     }
     for(size_t i = 0; i < m_lengthCollectionButtons.size(); i++) {
         std::vector<Button *> &children = m_lengthCollectionButtons[i]->getChildren();
-        std::sort(children.begin(), children.end(), comparatorWrapper);
+        std::sort(children.begin(), children.end(), m_sortingComparator);
         m_lengthCollectionButtons[i]->setChildren(children);
     }
     for(size_t i = 0; i < m_titleCollectionButtons.size(); i++) {
         std::vector<Button *> &children = m_titleCollectionButtons[i]->getChildren();
-        std::sort(children.begin(), children.end(), comparatorWrapper);
+        std::sort(children.begin(), children.end(), m_sortingComparator);
         m_titleCollectionButtons[i]->setChildren(children);
     }
 
@@ -3330,11 +3154,6 @@ void SongBrowser::onSortChangeInt(UString text, bool autoScroll) {
     onAfterSortingOrGroupChange(autoScroll);
 }
 
-void SongBrowser::onGroupTabButtonClicked(CBaseUIButton *groupTabButton) {
-    onGroupChange(groupTabButton->getText());
-    engine->getSound()->play(osu->getSkin()->m_clickButton);
-}
-
 void SongBrowser::onGroupNoGrouping() {
     m_group = GROUP::GROUP_NO_GROUPING;
 
@@ -3437,28 +3256,7 @@ void SongBrowser::onAfterSortingOrGroupChange(bool autoScroll) {
     if(m_bInSearch) onSearchUpdate();
 
     // (can't call it right here because we maybe have async)
-    m_bOnAfterSortingOrGroupChangeUpdateScheduledAutoScroll = autoScroll;
-    m_bOnAfterSortingOrGroupChangeUpdateScheduled = true;
-}
-
-void SongBrowser::onAfterSortingOrGroupChangeUpdateInt(bool autoScroll) {
-    // if anything was selected, scroll to that. otherwise scroll to top
-    const std::vector<CBaseUIElement *> &elements = m_songBrowser->getContainer()->getElements();
-    bool isAnythingSelected = false;
-    for(size_t i = 0; i < elements.size(); i++) {
-        const Button *button = dynamic_cast<Button *>(elements[i]);
-        if(button != NULL && button->isSelected()) {
-            isAnythingSelected = true;
-            break;
-        }
-    }
-
-    if(autoScroll) {
-        if(isAnythingSelected)
-            scrollToSelectedSongButton();
-        else
-            m_songBrowser->scrollToTop();
-    }
+    m_scheduled_scroll_to_selected_button = autoScroll;
 }
 
 void SongBrowser::onSelectionMode() {
@@ -3510,15 +3308,14 @@ void SongBrowser::onSelectionRandom() {
 void SongBrowser::onSelectionOptions() {
     engine->getSound()->play(osu->getSkin()->m_clickButton);
 
-    Button *currentlySelectedSongButton = findCurrentlySelectedSongButton();
-    if(currentlySelectedSongButton != NULL) {
-        scrollToSongButton(currentlySelectedSongButton);
+    if(m_selectedButton != NULL) {
+        scrollToSongButton(m_selectedButton);
 
         const Vector2 heuristicSongButtonPositionAfterSmoothScrollFinishes =
             (m_songBrowser->getPos() + m_songBrowser->getSize() / 2);
 
-        SongButton *songButtonPointer = dynamic_cast<SongButton *>(currentlySelectedSongButton);
-        CollectionButton *collectionButtonPointer = dynamic_cast<CollectionButton *>(currentlySelectedSongButton);
+        SongButton *songButtonPointer = dynamic_cast<SongButton *>(m_selectedButton);
+        CollectionButton *collectionButtonPointer = dynamic_cast<CollectionButton *>(m_selectedButton);
         if(songButtonPointer != NULL)
             songButtonPointer->triggerContextMenu(heuristicSongButtonPositionAfterSmoothScrollFinishes);
         else if(collectionButtonPointer != NULL)

+ 19 - 60
src/App/Osu/SongBrowser/SongBrowser.h

@@ -5,6 +5,8 @@
 class Beatmap;
 class Database;
 class DatabaseBeatmap;
+typedef DatabaseBeatmap BeatmapDifficulty;
+typedef DatabaseBeatmap BeatmapSet;
 class SkinImage;
 
 class UIContextMenu;
@@ -29,50 +31,19 @@ class ConVar;
 
 class SongBrowserBackgroundSearchMatcher;
 
+typedef bool (*SORTING_COMPARATOR)(const Button *a, const Button *b);
+bool sort_by_artist(const Button *a, const Button *b);
+bool sort_by_bpm(const Button *a, const Button *b);
+bool sort_by_creator(const Button *a, const Button *b);
+bool sort_by_date_added(const Button *a, const Button *b);
+bool sort_by_difficulty(const Button *a, const Button *b);
+bool sort_by_length(const Button *a, const Button *b);
+bool sort_by_title(const Button *a, const Button *b);
+
 class SongBrowser : public ScreenBackable {
    public:
     static void drawSelectedBeatmapBackgroundImage(Graphics *g, float alpha = 1.0f);
 
-    struct SORTING_COMPARATOR {
-        virtual ~SORTING_COMPARATOR() { ; }
-        virtual bool operator()(Button const *a, Button const *b) const = 0;
-    };
-
-    struct SortByArtist : public SORTING_COMPARATOR {
-        virtual ~SortByArtist() { ; }
-        virtual bool operator()(Button const *a, Button const *b) const;
-    };
-
-    struct SortByBPM : public SORTING_COMPARATOR {
-        virtual ~SortByBPM() { ; }
-        virtual bool operator()(Button const *a, Button const *b) const;
-    };
-
-    struct SortByCreator : public SORTING_COMPARATOR {
-        virtual ~SortByCreator() { ; }
-        virtual bool operator()(Button const *a, Button const *b) const;
-    };
-
-    struct SortByDateAdded : public SORTING_COMPARATOR {
-        virtual ~SortByDateAdded() { ; }
-        virtual bool operator()(Button const *a, Button const *b) const;
-    };
-
-    struct SortByDifficulty : public SORTING_COMPARATOR {
-        virtual ~SortByDifficulty() { ; }
-        virtual bool operator()(Button const *a, Button const *b) const;
-    };
-
-    struct SortByLength : public SORTING_COMPARATOR {
-        virtual ~SortByLength() { ; }
-        bool operator()(Button const *a, Button const *b) const;
-    };
-
-    struct SortByTitle : public SORTING_COMPARATOR {
-        virtual ~SortByTitle() { ; }
-        bool operator()(Button const *a, Button const *b) const;
-    };
-
     enum class GROUP {
         GROUP_NO_GROUPING,
         GROUP_ARTIST,
@@ -121,18 +92,16 @@ class SongBrowser : public ScreenBackable {
     void recalculateStarsForSelectedBeatmap(bool force = false);
 
     void refreshBeatmaps();
-    void addBeatmap(DatabaseBeatmap *beatmap);
+    void addBeatmapSet(BeatmapSet *beatmap);
+    void addSongButtonToAlphanumericGroup(SongButton *btn, std::vector<CollectionButton *> &group, const std::string &name);
 
     void requestNextScrollToSongButtonJumpFix(SongDifficultyButton *diffButton);
     void scrollToSongButton(Button *songButton, bool alignOnTop = false);
-    void scrollToSelectedSongButton();
     void rebuildSongButtons();
     void recreateCollectionsButtons();
     void rebuildScoreButtons();
     void updateSongButtonLayout();
-    void updateSongButtonSorting();
 
-    Button *findCurrentlySelectedSongButton() const;
     inline const std::vector<CollectionButton *> &getCollectionButtons() const { return m_collectionButtons; }
 
     inline bool hasSelectedAndIsPlaying() const { return m_bHasSelectedAndIsPlaying; }
@@ -153,14 +122,14 @@ class SongBrowser : public ScreenBackable {
         SORT_DATEADDED,
         SORT_DIFFICULTY,
         SORT_LENGTH,
+        SORT_TITLE,
         SORT_RANKACHIEVED,
-        SORT_TITLE
     };
 
     struct SORTING_METHOD {
         SORT type;
         UString name;
-        SORTING_COMPARATOR *comparator;
+        SORTING_COMPARATOR comparator;
     };
 
     struct GROUPING {
@@ -183,9 +152,6 @@ class SongBrowser : public ScreenBackable {
 
     UISelectionButton *addBottombarNavButton(std::function<SkinImage *()> getImageFunc,
                                              std::function<SkinImage *()> getImageOverFunc);
-    CBaseUIButton *addTopBarRightTabButton(UString text);
-    CBaseUIButton *addTopBarRightGroupButton(UString text);
-    CBaseUIButton *addTopBarRightSortButton(UString text);
     CBaseUIButton *addTopBarLeftTabButton(UString text);
     CBaseUIButton *addTopBarLeftButton(UString text);
 
@@ -206,7 +172,6 @@ class SongBrowser : public ScreenBackable {
     void onSortChange(UString text, int id = -1);
     void onSortChangeInt(UString text, bool autoScroll);
 
-    void onGroupTabButtonClicked(CBaseUIButton *groupTabButton);
     void onGroupNoGrouping();
     void onGroupCollections(bool autoScroll = true);
     void onGroupArtist();
@@ -218,7 +183,6 @@ class SongBrowser : public ScreenBackable {
     void onGroupTitle();
 
     void onAfterSortingOrGroupChange(bool autoScroll = true);
-    void onAfterSortingOrGroupChangeUpdateInt(bool autoScroll);
 
     void onSelectionMode();
     void onSelectionMods();
@@ -257,6 +221,8 @@ class SongBrowser : public ScreenBackable {
 
     GROUP m_group;
     std::vector<GROUPING> m_groupings;
+
+    SORTING_COMPARATOR m_sortingComparator;
     SORT m_sortingMethod;
     std::vector<SORTING_METHOD> m_sortingMethods;
 
@@ -273,15 +239,8 @@ class SongBrowser : public ScreenBackable {
 
     // top bar right
     CBaseUIContainer *m_topbarRight;
-    std::vector<CBaseUIButton *> m_topbarRightTabButtons;
-    std::vector<CBaseUIButton *> m_topbarRightGroupButtons;
-    std::vector<CBaseUIButton *> m_topbarRightSortButtons;
     CBaseUILabel *m_groupLabel;
     CBaseUIButton *m_groupButton;
-    CBaseUIButton *m_noGroupingButton;
-    CBaseUIButton *m_collectionsButton;
-    CBaseUIButton *m_artistButton;
-    CBaseUIButton *m_difficultiesButton;
     CBaseUILabel *m_sortLabel;
     CBaseUIButton *m_sortButton;
     UIContextMenu *m_contextMenu;
@@ -302,10 +261,12 @@ class SongBrowser : public ScreenBackable {
 
     // song browser
     CBaseUIScrollView *m_songBrowser;
+    Button *m_selectedButton = NULL;
     bool m_bSongBrowserRightClickScrollCheck;
     bool m_bSongBrowserRightClickScrolling;
     bool m_bNextScrollToSongButtonJumpFixScheduled;
     bool m_bNextScrollToSongButtonJumpFixUseScrollSizeDelta;
+    bool m_scheduled_scroll_to_selected_button = false;
     float m_fNextScrollToSongButtonJumpFixOldRelPosY;
     float m_fNextScrollToSongButtonJumpFixOldScrollSizeY;
 
@@ -363,6 +324,4 @@ class SongBrowser : public ScreenBackable {
     bool m_bInSearch;
     GROUP m_searchPrevGroup;
     SongBrowserBackgroundSearchMatcher *m_backgroundSearchMatcher;
-    bool m_bOnAfterSortingOrGroupChangeUpdateScheduled;
-    bool m_bOnAfterSortingOrGroupChangeUpdateScheduledAutoScroll;
 };

+ 1 - 1
src/App/Osu/SongBrowser/SongButton.cpp

@@ -217,7 +217,7 @@ void SongButton::drawSubTitle(Graphics *g, float deselectedAlpha, bool forceSele
     g->popTransform();
 }
 
-void SongButton::sortChildren() { std::sort(m_children.begin(), m_children.end(), SongBrowser::SortByDifficulty()); }
+void SongButton::sortChildren() { std::sort(m_children.begin(), m_children.end(), sort_by_difficulty); }
 
 void SongButton::updateLayoutEx() {
     Button::updateLayoutEx();

+ 1 - 1
src/Engine/Main/main_Windows.cpp

@@ -221,7 +221,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
                         continue;
                     }
 
-                    osu->getSongBrowser()->getDatabase()->addBeatmap(mapset_dir);
+                    osu->getSongBrowser()->getDatabase()->addBeatmapSet(mapset_dir);
                     if(!osu->getSongBrowser()->selectBeatmapset(set_id)) {
                         osu->getNotificationOverlay()->addNotification("Failed to import beatmapset");
                         continue;

+ 0 - 3
src/Engine/Sound.cpp

@@ -438,9 +438,6 @@ u32 Sound::getPositionMS() {
         delta = (engine->getTime() - m_fLastPlayTime) * speedMultiplier;
         f64 lerpPercent = clamp<f64>(((delta / interpDuration) - interp_ratio) / (1.0 - interp_ratio), 0.0, 1.0);
         positionMS = (u32)lerp<f64>(delta * 1000.0, (f64)positionMS, lerpPercent);
-        if(pre_interp_pos != positionMS) {
-            debugLog("Interpolating music position! %d ms -> %d ms\n", pre_interp_pos, positionMS);
-        }
     }
 
     return positionMS;