1
0

5 Commitit 7f49175a5e ... 8bc6f41e94

Tekijä SHA1 Viesti Päivämäärä
  kiwec 8bc6f41e94 Correct pp display on score screen 3 kuukautta sitten
  kiwec 6f93935193 Add tooltip to explain why controls are disabled 3 kuukautta sitten
  kiwec fa80119687 Attempt to unscuff the GUI framework 3 kuukautta sitten
  kiwec dbb025b58f Scroll to imported beatmap after import 3 kuukautta sitten
  kiwec ffaedd176d Rename setRelPos to setScrollPos 3 kuukautta sitten

+ 0 - 1
neosu.vcxproj

@@ -351,7 +351,6 @@ copy $(SolutionDir)libraries\libcurl\curl-ca-bundle.crt $(SolutionDir)build\$(Pl
     <ClCompile Include="src\gui\CBaseUIButton.cpp" />
     <ClCompile Include="src\gui\CBaseUICheckbox.cpp" />
     <ClCompile Include="src\gui\CBaseUIContainer.cpp" />
-    <ClCompile Include="src\gui\CBaseUIContainerBase.cpp" />
     <ClCompile Include="src\gui\CBaseUIElement.cpp" />
     <ClCompile Include="src\gui\CBaseUIImage.cpp" />
     <ClCompile Include="src\gui\CBaseUIImageButton.cpp" />

+ 1 - 1
src/App/Osu/Chat.cpp

@@ -868,7 +868,7 @@ void Chat::updateButtonLayout(Vector2 screen) {
 
     std::sort(m_channels.begin(), m_channels.end(), [](ChatChannel *a, ChatChannel *b) { return a->name < b->name; });
 
-    // Look, I really tried. But for some reason setRelPos() doesn't work until we change
+    // Look, I really tried. But for some reason setPos() doesn't work until we change
     // the screen resolution once. So I'll just compute absolute position manually.
     float button_container_height = button_height + 2;
     for(auto chan : m_channels) {

+ 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);

+ 60 - 48
src/App/Osu/RankingScreen.cpp

@@ -29,6 +29,7 @@
 #include "UIButton.h"
 #include "UIRankingScreenInfoLabel.h"
 #include "UIRankingScreenRankingPanel.h"
+#include "pp.h"
 #include "score.h"
 
 ConVar osu_rankingscreen_topbar_height_percent("osu_rankingscreen_topbar_height_percent", 0.785f, FCVAR_DEFAULT);
@@ -189,10 +190,10 @@ RankingScreen::RankingScreen() : ScreenBackable() {
 
     m_retry_btn = new UIButton(0, 0, 0, 0, "", "Retry");
     m_retry_btn->setClickCallback(fastdelegate::MakeDelegate(this, &RankingScreen::onRetryClicked));
-    addBaseUIElement(m_retry_btn);
+    m_rankings->getContainer()->addBaseUIElement(m_retry_btn);
     m_watch_btn = new UIButton(0, 0, 0, 0, "", "Watch replay");
     m_watch_btn->setClickCallback(fastdelegate::MakeDelegate(this, &RankingScreen::onWatchClicked));
-    addBaseUIElement(m_watch_btn);
+    m_rankings->getContainer()->addBaseUIElement(m_watch_btn);
 
     setGrade(FinishedScore::Grade::D);
     setIndex(0);  // TEMP
@@ -238,13 +239,20 @@ void RankingScreen::draw(Graphics *g) {
     if(!m_bVisible) return;
 
     // draw background image
-    if(osu_draw_rankingscreen_background_image.getBool()) SongBrowser::drawSelectedBeatmapBackgroundImage(g);
+    if(osu_draw_rankingscreen_background_image.getBool()) {
+        SongBrowser::drawSelectedBeatmapBackgroundImage(g);
 
-    m_rankings->draw(g);
+        // draw top black bar
+        g->setColor(0xff000000);
+        g->fillRect(0, 0, osu->getScreenWidth(),
+                    m_rankingTitle->getSize().y * osu_rankingscreen_topbar_height_percent.getFloat());
+    }
+
+    ScreenBackable::draw(g);
 
     // draw active mods
     const Vector2 modPosStart =
-        Vector2(m_rankings->getSize().x - osu->getUIScale(20), m_rankings->getScrollPosY() + osu->getUIScale(260));
+        Vector2(m_rankings->getSize().x - osu->getUIScale(20), m_rankings->getRelPosY() + osu->getUIScale(260));
     Vector2 modPos = modPosStart;
     Vector2 modPosMax;
     if(m_bModTD) drawModImage(g, osu->getSkin()->getSelectionModTD(), modPos, modPosMax);
@@ -291,13 +299,11 @@ void RankingScreen::draw(Graphics *g) {
         const int backgroundWidth = maxStringWidth + 2 * backgroundMargin;
         const int backgroundHeight = experimentalModHeight * m_enabledExperimentalMods.size() + 2 * backgroundMargin;
 
-        // draw background
         g->setColor(0x77000000);
         g->fillRect((int)experimentalModPos.x - backgroundMargin,
                     (int)experimentalModPos.y - experimentalModFont->getHeight() - backgroundMargin, backgroundWidth,
                     backgroundHeight);
 
-        // draw mods
         g->pushTransform();
         {
             g->translate((int)experimentalModPos.x, (int)experimentalModPos.y);
@@ -318,7 +324,7 @@ void RankingScreen::draw(Graphics *g) {
     }
 
     // draw pp
-    if(osu_rankingscreen_pp.getBool() && !m_bIsLegacyScore) {
+    if(osu_rankingscreen_pp.getBool()) {
         const UString ppString = getPPString();
         const Vector2 ppPos = getPPPosRaw();
 
@@ -333,16 +339,6 @@ void RankingScreen::draw(Graphics *g) {
         }
         g->popTransform();
     }
-
-    // draw top black bar
-    g->setColor(0xff000000);
-    g->fillRect(0, 0, osu->getScreenWidth(),
-                m_rankingTitle->getSize().y * osu_rankingscreen_topbar_height_percent.getFloat());
-
-    m_rankingTitle->draw(g);
-    m_songInfo->draw(g);
-
-    ScreenBackable::draw(g);
 }
 
 void RankingScreen::drawModImage(Graphics *g, SkinImage *image, Vector2 &pos, Vector2 &max) {
@@ -390,6 +386,8 @@ CBaseUIContainer *RankingScreen::setVisible(bool visible) {
 
     if(m_bVisible) {
         m_backButton->resetAnimation();
+        m_rankings->scrollToY(0, false);
+
         updateLayout();
     } else {
         // Stop applause sound
@@ -413,7 +411,7 @@ CBaseUIContainer *RankingScreen::setVisible(bool visible) {
 }
 
 void RankingScreen::onRetryClicked() {
-    // TODO @kiwec: test this
+    // TODO @kiwec: doesn't work, just backs out to song browser, idk why
     setVisible(false);
     osu->getSelectedBeatmap()->play();
 }
@@ -429,12 +427,15 @@ void RankingScreen::setScore(FinishedScore score) {
     bool is_same_player = !score.playerName.compare(current_name.toUtf8());
 
     m_score = score;
-    // TODO @kiwec: broken
-    m_retry_btn->setVisible(false);
-    m_watch_btn->setVisible(false);
+
+    // TODO @kiwec: buttons don't work correctly
     // m_retry_btn->setVisible(is_same_player && !bancho.is_in_a_multi_room());
     // m_watch_btn->setVisible(score.has_replay && !bancho.is_in_a_multi_room());
 
+    // TODO @kiwec: i can't even setVisible(false) man this is so cancer
+    m_retry_btn->setVisible(false);
+    m_watch_btn->setVisible(false);
+
     m_bIsLegacyScore = score.isLegacyScore;
     m_bIsImportedLegacyScore = score.isImportedLegacyScore;
     m_bIsUnranked = false;
@@ -458,16 +459,26 @@ void RankingScreen::setScore(FinishedScore score) {
     m_fStarsTomTotal = score.starsTomTotal;
     m_fStarsTomAim = score.starsTomAim;
     m_fStarsTomSpeed = score.starsTomSpeed;
-    m_fPPv2 = score.pp;
-
     m_fSpeedMultiplier = std::round(score.speedMultiplier * 100.0f) / 100.0f;
+
+    if(!score.isLegacyScore) {
+        m_fSpeedMultiplier = 1.f;
+        if(score.modsLegacy & ModFlags::DoubleTime) {
+            m_fSpeedMultiplier = 1.5f;
+        } else if(score.modsLegacy & ModFlags::HalfTime) {
+            m_fSpeedMultiplier = 0.75f;
+        }
+
+        m_fPPv2 = score.pp;
+    }
+
     m_fCS = std::round(score.CS * 100.0f) / 100.0f;
     m_fAR = std::round(GameRules::getRawApproachRateForSpeedMultiplier(GameRules::getRawApproachTime(score.AR),
-                                                                       score.speedMultiplier) *
+                                                                       m_fSpeedMultiplier) *
                        100.0f) /
             100.0f;
     m_fOD = std::round(GameRules::getRawOverallDifficultyForSpeedMultiplier(GameRules::getRawHitWindow300(score.OD),
-                                                                            score.speedMultiplier) *
+                                                                            m_fSpeedMultiplier) *
                        100.0f) /
             100.0f;
     m_fHP = std::round(score.HP * 100.0f) / 100.0f;
@@ -517,12 +528,18 @@ void RankingScreen::setBeatmapInfo(Beatmap *beatmap, DatabaseBeatmap *diff2) {
     UString local_name = convar->getConVarByName("name")->getString();
     m_songInfo->setPlayer(m_bIsUnranked ? "neosu" : local_name.toUtf8());
 
-    // round all here to 2 decimal places
-    m_fSpeedMultiplier = std::round(osu->getSpeedMultiplier() * 100.0f) / 100.0f;
-    m_fCS = std::round(beatmap->getCS() * 100.0f) / 100.0f;
-    m_fAR = std::round(GameRules::getApproachRateForSpeedMultiplier(beatmap) * 100.0f) / 100.0f;
-    m_fOD = std::round(GameRules::getOverallDifficultyForSpeedMultiplier(beatmap) * 100.0f) / 100.0f;
-    m_fHP = std::round(beatmap->getHP() * 100.0f) / 100.0f;
+    if(m_score.isLegacyScore) {
+        gradual_pp *pp = init_gradual_pp(diff2, m_score.modsLegacy, m_fAR, m_fCS, m_fOD, m_fSpeedMultiplier);
+        pp = calculate_gradual_pp(pp, diff2->getNumObjects(), m_score.comboMax, m_score.num300s, m_score.num100s,
+                                  m_score.num50s, m_score.numMisses, &m_fStarsTomTotal, &m_fPPv2);
+        free_gradual_pp(pp);
+
+        // round all here to 2 decimal places
+        m_fCS = std::round(beatmap->getCS() * 100.0f) / 100.0f;
+        m_fAR = std::round(GameRules::getApproachRateForSpeedMultiplier(beatmap) * 100.0f) / 100.0f;
+        m_fOD = std::round(GameRules::getOverallDifficultyForSpeedMultiplier(beatmap) * 100.0f) / 100.0f;
+        m_fHP = std::round(beatmap->getHP() * 100.0f) / 100.0f;
+    }
 }
 
 void RankingScreen::updateLayout() {
@@ -543,13 +560,14 @@ void RankingScreen::updateLayout() {
                         max(m_songInfo->getMinimumHeight(),
                             m_rankingTitle->getSize().y * osu_rankingscreen_topbar_height_percent.getFloat()));
 
-    // TODO @kiwec: buttons are not placed at the given coords, idk why
-    m_retry_btn->setSize(150 * uiScale, 50 * uiScale);
-    m_watch_btn->setSize(150 * uiScale, 50 * uiScale);
-    m_retry_btn->setPos(osu->getScreenSize().x - (150 * uiScale + 10.f * uiScale),
-                        osu->getScreenSize().y - (50 * uiScale + 10.f * uiScale));
-    m_watch_btn->setPos(osu->getScreenSize().x - (150 * uiScale + 10.f * uiScale),
-                        osu->getScreenSize().y - (50 * uiScale * 2.f + 20.f * uiScale));
+    float btn_width = 150 * uiScale;
+    float btn_height = 50 * uiScale;
+    m_retry_btn->setSize(btn_width, btn_height);
+    m_watch_btn->setSize(btn_width, btn_height);
+    m_watch_btn->setRelPos(osu->getScreenSize().x - (btn_width + 10.f * uiScale),
+                           osu->getScreenSize().y - (btn_height + 130.f * uiScale));
+    m_retry_btn->setRelPos(osu->getScreenSize().x - (btn_width + 10.f * uiScale),
+                           m_watch_btn->getRelPos().y - (btn_height + 5.f * uiScale));
 
     m_rankings->setSize(osu->getScreenSize().x + 2, osu->getScreenSize().y - m_songInfo->getSize().y + 3);
     m_rankings->setRelPosY(m_songInfo->getSize().y - 1);
@@ -653,13 +671,7 @@ UString RankingScreen::getPPString() { return UString::format("%ipp", (int)(std:
 Vector2 RankingScreen::getPPPosRaw() {
     const UString ppString = getPPString();
     float ppStringWidth = osu->getTitleFont()->getStringWidth(ppString);
-    return Vector2(m_rankingGrade->getPos().x, 0) +
-           Vector2(m_rankingGrade->getSize().x / 2 - ppStringWidth / 2,
-                   m_rankings->getScrollPosY() + osu->getUIScale(400) + osu->getTitleFont()->getHeight() / 2);
-}
-
-Vector2 RankingScreen::getPPPosCenterRaw() {
-    return Vector2(m_rankingGrade->getPos().x, 0) +
-           Vector2(m_rankingGrade->getSize().x / 2,
-                   m_rankings->getScrollPosY() + osu->getUIScale(400) + osu->getTitleFont()->getHeight() / 2);
+    return Vector2(m_rankingGrade->getPos().x, Osu::ui_scale->getFloat() * 10.f) +
+           Vector2(m_rankingGrade->getSize().x / 2 - (ppStringWidth / 2 + Osu::ui_scale->getFloat() * 100.f),
+                   m_rankings->getRelPosY() + osu->getUIScale(400) + osu->getTitleFont()->getHeight() / 2);
 }

+ 2 - 3
src/App/Osu/RankingScreen.h

@@ -45,7 +45,6 @@ class RankingScreen : public ScreenBackable {
 
     UString getPPString();
     Vector2 getPPPosRaw();
-    Vector2 getPPPosCenterRaw();
 
     ConVar *m_osu_scores_enabled;
 
@@ -66,10 +65,10 @@ class RankingScreen : public ScreenBackable {
     float m_fHitErrorAvgMin;
     float m_fHitErrorAvgMax;
 
-    float m_fStarsTomTotal;
+    f64 m_fStarsTomTotal;
     float m_fStarsTomAim;
     float m_fStarsTomSpeed;
-    float m_fPPv2;
+    f64 m_fPPv2;
 
     float m_fSpeedMultiplier;
     float m_fCS;

+ 0 - 1
src/App/Osu/SongBrowser/Button.cpp

@@ -233,7 +233,6 @@ Button *Button::setVisible(bool visible) {
         m_fCenterOffsetVelocityAnimation = centerOffsetVelocityAnimationTarget;
 
         // force early layout update
-        updateLayout();
         updateLayoutEx();
     }
 

+ 163 - 366
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)
@@ -1842,29 +1792,13 @@ void SongBrowser::scrollToSongButton(Button *songButton, bool alignOnTop) {
                         m_fNextScrollToSongButtonJumpFixOldScrollSizeY;  // technically not correct but feels a lot
                                                                          // better for KEY_LEFT navigation
         }
-        m_songBrowser->scrollToY(m_songBrowser->getScrollPosY() - delta, false);
+        m_songBrowser->scrollToY(m_songBrowser->getRelPosY() - delta, false);
     }
 
     m_songBrowser->scrollToY(-songButton->getRelPos().y +
                              (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]->setRelPos(
-            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->setRelPos(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]->setRelPos(
-            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]->setRelPos(
-            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->setRelPos(
-            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->setRelPos(
-            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->setRelPos(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->setRelPos(
+        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->setRelPos(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->setRelPos(
+        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->setRelPosX(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)
@@ -3673,7 +3470,7 @@ void SongBrowser::onSongButtonContextMenu(SongButton *songButton, UString text,
     }
 
     if(updateUIScheduled) {
-        const float prevScrollPosY = m_songBrowser->getScrollPosY();  // usability
+        const float prevScrollPosY = m_songBrowser->getRelPosY();  // usability
         const auto previouslySelectedCollectionName =
             (m_selectionPreviousCollectionButton != NULL ? m_selectionPreviousCollectionButton->getCollectionName()
                                                          : "");  // usability

+ 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;

+ 1 - 16
src/GUI/CBaseUIBoxShadow.h

@@ -1,15 +1,4 @@
-//================ Copyright (c) 2013, PG, All rights reserved. =================//
-//
-// Purpose:		box shadows
-//
-// $NoKeywords: $bshad
-//===============================================================================//
-
-// TODO: fix this
-
-#ifndef CBASEUIBOXSHADOW_H
-#define CBASEUIBOXSHADOW_H
-
+#pragma once
 #include "CBaseUIElement.h"
 
 class Shader;
@@ -23,8 +12,6 @@ class CBaseUIBoxShadow : public CBaseUIElement {
                      float ySize = 0, UString name = "");
     virtual ~CBaseUIBoxShadow();
 
-    ELEMENT_BODY(CBaseUIBoxShadow)
-
     virtual void draw(Graphics *g);
     void renderOffscreen(Graphics *g);
 
@@ -79,5 +66,3 @@ class GaussianBlur {
     GaussianBlurKernel *m_kernel;
     Shader *m_blurShader;
 };
-
-#endif

+ 0 - 2
src/GUI/CBaseUIButton.h

@@ -9,8 +9,6 @@ class CBaseUIButton : public CBaseUIElement {
                   UString text = "");
     virtual ~CBaseUIButton() { ; }
 
-    ELEMENT_BODY(CBaseUIButton);
-
     virtual void draw(Graphics *g);
 
     void click() { onClicked(); }

+ 1 - 14
src/GUI/CBaseUICheckbox.h

@@ -1,13 +1,4 @@
-//================ Copyright (c) 2014, PG, All rights reserved. =================//
-//
-// Purpose:		a simple checkbox
-//
-// $NoKeywords: $
-//===============================================================================//
-
-#ifndef CBASEUICHECKBOX_H
-#define CBASEUICHECKBOX_H
-
+#pragma once
 #include "CBaseUIButton.h"
 
 class CBaseUICheckbox : public CBaseUIButton {
@@ -16,8 +7,6 @@ class CBaseUICheckbox : public CBaseUIButton {
                     UString text = "");
     virtual ~CBaseUICheckbox() { ; }
 
-    ELEMENT_BODY(CBaseUICheckbox)
-
     virtual void draw(Graphics *g);
 
     inline float getBlockSize() { return m_vSize.y / 2; }
@@ -40,5 +29,3 @@ class CBaseUICheckbox : public CBaseUIButton {
     bool m_bChecked;
     CheckboxChangeCallback m_changeCallback;
 };
-
-#endif

+ 2 - 6
src/GUI/CBaseUIContainer.cpp

@@ -131,7 +131,7 @@ void CBaseUIContainer::draw(Graphics *g) {
     if(!m_bVisible) return;
 
     for(size_t i = 0; i < m_vElements.size(); i++) {
-        if(!m_vElements[i]->isDrawnManually()) m_vElements[i]->draw(g);
+        m_vElements[i]->draw(g);
     }
 }
 
@@ -154,14 +154,10 @@ void CBaseUIContainer::mouse_update(bool *propagate_clicks) {
 
 void CBaseUIContainer::update_pos() {
     for(size_t i = 0; i < m_vElements.size(); i++) {
-        if(!m_vElements[i]->isPositionedManually()) m_vElements[i]->setPos(m_vPos + m_vElements[i]->getRelPos());
+        m_vElements[i]->setPos(m_vPos + m_vElements[i]->getRelPos());
     }
 }
 
-void CBaseUIContainer::update_pos(CBaseUIElement *element) {
-    if(element != NULL) element->setPos(m_vPos + element->getRelPos());
-}
-
 void CBaseUIContainer::onKeyUp(KeyboardEvent &e) {
     for(size_t i = 0; i < m_vElements.size(); i++) {
         if(m_vElements[i]->isVisible()) m_vElements[i]->onKeyUp(e);

+ 1 - 15
src/GUI/CBaseUIContainer.h

@@ -1,13 +1,4 @@
-//================ Copyright (c) 2011, PG, All rights reserved. =================//
-//
-// Purpose:		a container for UI elements
-//
-// $NoKeywords: $
-//===============================================================================//
-
-#ifndef CBASEUICONTAINER_H
-#define CBASEUICONTAINER_H
-
+#pragma once
 #include "CBaseUIElement.h"
 
 class CBaseUIContainer : public CBaseUIElement {
@@ -15,8 +6,6 @@ class CBaseUIContainer : public CBaseUIElement {
     CBaseUIContainer(float xPos = 0, float yPos = 0, float xSize = 0, float ySize = 0, UString name = "");
     virtual ~CBaseUIContainer();
 
-    ELEMENT_BODY(CBaseUIContainer)
-
     void clear();
     void empty();
 
@@ -56,10 +45,7 @@ class CBaseUIContainer : public CBaseUIElement {
     virtual void onDisabled();
 
     void update_pos();
-    void update_pos(CBaseUIElement *element);
 
    protected:
     std::vector<CBaseUIElement *> m_vElements;
 };
-
-#endif

+ 0 - 207
src/GUI/CBaseUIContainerBase.cpp

@@ -1,207 +0,0 @@
-#include "CBaseUIContainerBase.h"
-
-#include "Engine.h"
-
-using namespace std;
-
-CBaseUIContainerBase::CBaseUIContainerBase(UString name) : CBaseUIElement(0, 0, 0, 0, name) { m_bClipping = false; }
-
-CBaseUIContainerBase::~CBaseUIContainerBase() {}
-
-void CBaseUIContainerBase::empty() {
-    for(size_t i = 0; i < m_vElements.size(); i++) m_vElements[i]->setParent(NULL);
-
-    m_vElements = std::vector<std::shared_ptr<CBaseUIElement>>();
-}
-
-CBaseUIContainerBase *CBaseUIContainerBase::addElement(CBaseUIElement *element, bool back) {
-    if(element == NULL) return this;
-
-    element->setParent(this);
-    std::shared_ptr<CBaseUIElement> buffer(element);
-
-    if(back)
-        m_vElements.insert(m_vElements.begin(), buffer);
-    else
-        m_vElements.push_back(buffer);
-
-    updateElement(element);
-    return this;
-}
-
-CBaseUIContainerBase *CBaseUIContainerBase::addElement(std::shared_ptr<CBaseUIElement> element, bool back) {
-    if(element == NULL || element.get() == NULL) return this;
-
-    element->setParent(this);
-
-    if(back)
-        m_vElements.insert(m_vElements.begin(), element);
-    else
-        m_vElements.push_back(element);
-
-    updateElement(element.get());
-    return this;
-}
-
-CBaseUIContainerBase *CBaseUIContainerBase::insertElement(CBaseUIElement *element, CBaseUIElement *index, bool back) {
-    if(element == NULL || index == NULL) return this;
-
-    for(size_t i = 0; i < m_vElements.size(); i++) {
-        if(m_vElements[i].get() == index) {
-            std::shared_ptr<CBaseUIElement> buffer(element);
-            element->setParent(this);
-
-            if(back)
-                m_vElements.insert(m_vElements.begin() + clamp<int>(i + 1, 0, m_vElements.size()), buffer);
-            else
-                m_vElements.insert(m_vElements.begin() + clamp<int>(i, 0, m_vElements.size()), buffer);
-
-            updateElement(element);
-            return this;
-        }
-    }
-
-    debugLog("Warning: %s::insertSlot() couldn't find index: %s\n", getName().toUtf8(), index->getName().toUtf8());
-
-    return this;
-}
-
-CBaseUIContainerBase *CBaseUIContainerBase::insertElement(CBaseUIElement *element,
-                                                          std::shared_ptr<CBaseUIElement> index, bool back) {
-    if(element == NULL || index == NULL || index.get() == NULL) return this;
-
-    for(size_t i = 0; i < m_vElements.size(); i++) {
-        if(m_vElements[i] == index) {
-            std::shared_ptr<CBaseUIElement> buffer(element);
-            element->setParent(this);
-
-            if(back)
-                m_vElements.insert(m_vElements.begin() + clamp<int>(i + 1, 0, m_vElements.size()), buffer);
-            else
-                m_vElements.insert(m_vElements.begin() + clamp<int>(i, 0, m_vElements.size()), buffer);
-
-            updateElement(element);
-            return this;
-        }
-    }
-
-    return this;
-}
-
-CBaseUIContainerBase *CBaseUIContainerBase::insertElement(std::shared_ptr<CBaseUIElement> element,
-                                                          CBaseUIElement *index, bool back) {
-    if(element == NULL || element.get() == NULL || index == NULL) return this;
-
-    for(size_t i = 0; i < m_vElements.size(); i++) {
-        if(m_vElements[i].get() == index) {
-            element.get()->setParent(this);
-
-            if(back)
-                m_vElements.insert(m_vElements.begin() + clamp<int>(i + 1, 0, m_vElements.size()), element);
-            else
-                m_vElements.insert(m_vElements.begin() + clamp<int>(i, 0, m_vElements.size()), element);
-
-            updateElement(element.get());
-            return this;
-        }
-    }
-
-    return this;
-}
-
-CBaseUIContainerBase *CBaseUIContainerBase::insertElement(std::shared_ptr<CBaseUIElement> element,
-                                                          std::shared_ptr<CBaseUIElement> index, bool back) {
-    if(element == NULL || index == NULL || element.get() == NULL || index.get() == NULL) return this;
-
-    for(size_t i = 0; i < m_vElements.size(); i++) {
-        if(m_vElements[i] == index) {
-            element->setParent(this);
-            if(back)
-                m_vElements.insert(m_vElements.begin() + clamp<int>(i + 1, 0, m_vElements.size()), element);
-            else
-                m_vElements.insert(m_vElements.begin() + clamp<int>(i, 0, m_vElements.size()), element);
-            updateElement(element.get());
-            return this;
-        }
-    }
-
-    debugLog("Warning: %s::insertSlot() couldn't find index: %s\n", getName().toUtf8(),
-             index.get()->getName().toUtf8());
-
-    return this;
-}
-
-void CBaseUIContainerBase::removeElement(CBaseUIElement *element) {
-    for(size_t i = 0; i < m_vElements.size(); i++) {
-        if(m_vElements[i].get() == element) {
-            m_vElements[i]->setParent(NULL);
-            m_vElements.erase(m_vElements.begin() + i);
-            updateLayout();
-            return;
-        }
-    }
-
-    debugLog("Warning: CBaseUIContainerBase::removeElement() couldn't find element\n");
-}
-
-void CBaseUIContainerBase::removeElement(std::shared_ptr<CBaseUIElement> element) {
-    for(size_t i = 0; i < m_vElements.size(); i++) {
-        if(m_vElements[i] == element) {
-            m_vElements[i]->setParent(NULL);
-            m_vElements.erase(m_vElements.begin() + i);
-            updateLayout();
-            return;
-        }
-    }
-
-    debugLog("Warning: CBaseUIContainerBase::removeElement() couldn't find element\n");
-}
-
-CBaseUIElement *CBaseUIContainerBase::getElementByName(UString name, bool searchNestedContainers) {
-    for(size_t i = 0; i < m_vElements.size(); i++) {
-        if(m_vElements[i]->getName() == name)
-            return m_vElements[i].get();
-
-        else if(searchNestedContainers) {
-            CBaseUIContainerBase *container = dynamic_cast<CBaseUIContainerBase *>(m_vElements[i].get());
-            if(container != NULL) return container->getElementByName(name, true);
-        }
-    }
-
-    debugLog("Error: CBaseUIContainerBase::getSlotByElementName() \"%s\" does not exist!!!\n", name.toUtf8());
-    return NULL;
-}
-
-std::shared_ptr<CBaseUIElement> CBaseUIContainerBase::getElementSharedByName(UString name,
-                                                                             bool searchNestedContainers) {
-    for(size_t i = 0; i < m_vElements.size(); i++) {
-        if(m_vElements[i]->getName() == name)
-            return m_vElements[i];
-
-        else if(searchNestedContainers) {
-            CBaseUIContainerBase *container = dynamic_cast<CBaseUIContainerBase *>(m_vElements[i].get());
-            if(container != NULL) return container->getElementSharedByName(name, true);
-        }
-    }
-
-    debugLog("Error: CBaseUIContainerBase::getSlotByElementName() \"%s\" does not exist!!!\n", name.toUtf8());
-    return NULL;
-}
-
-void CBaseUIContainerBase::draw(Graphics *g) {
-    if(!m_bVisible) return;
-
-    if(m_bClipping) g->pushClipRect(McRect(m_vPos.x + 1, m_vPos.y + 1, m_vSize.x - 1, m_vSize.y));
-
-    for(size_t i = 0; i < m_vElements.size(); i++) {
-        m_vElements[i]->draw(g);
-    }
-
-    if(m_bClipping) g->popClipRect();
-}
-
-void CBaseUIContainerBase::mouse_update(bool *propagate_clicks) {
-    if(!m_bVisible) return;
-
-    for(size_t i = 0; i < m_vElements.size(); i++) m_vElements[i]->mouse_update(propagate_clicks);
-}

+ 0 - 92
src/GUI/CBaseUIContainerBase.h

@@ -1,92 +0,0 @@
-/*
- * CBaseUIContainerBase.h
- *
- *  Created on: May 31, 2017
- *      Author: Psy
- */
-
-#ifndef GUI_CBASEUICONTAINERBASE_H_
-#define GUI_CBASEUICONTAINERBASE_H_
-
-#define CONTAINER_BODY(T)                                                                                         \
-    ELEMENT_BODY(T)                                                                                               \
-                                                                                                                  \
-    virtual T *addElement(CBaseUIElement *element, bool back = false) {                                           \
-        CBaseUIContainerBase::addElement(element, back);                                                          \
-        return this;                                                                                              \
-    }                                                                                                             \
-    virtual T *addElement(std::shared_ptr<CBaseUIElement> element, bool back = false) {                           \
-        CBaseUIContainerBase::addElement(element, back);                                                          \
-        return this;                                                                                              \
-    }                                                                                                             \
-    virtual T *insertElement(CBaseUIElement *element, CBaseUIElement *index, bool back = false) {                 \
-        CBaseUIContainerBase::insertElement(element, index, back);                                                \
-        return this;                                                                                              \
-    }                                                                                                             \
-    virtual T *insertElement(std::shared_ptr<CBaseUIElement> element, CBaseUIElement *index, bool back = false) { \
-        CBaseUIContainerBase::insertElement(element, index, back);                                                \
-        return this;                                                                                              \
-    }                                                                                                             \
-    virtual T *insertElement(CBaseUIElement *element, std::shared_ptr<CBaseUIElement> index, bool back = false) { \
-        CBaseUIContainerBase::insertElement(element, index, back);                                                \
-        return this;                                                                                              \
-    }                                                                                                             \
-    virtual T *insertElement(std::shared_ptr<CBaseUIElement> element, std::shared_ptr<CBaseUIElement> index,      \
-                             bool back = false) {                                                                 \
-        CBaseUIContainerBase::insertElement(element, index, back);                                                \
-        return this;                                                                                              \
-    }                                                                                                             \
-    virtual T *setClipping(bool clipping) {                                                                       \
-        CBaseUIContainerBase::setClipping(clipping);                                                              \
-        return this;                                                                                              \
-    }
-
-#include "CBaseUIElement.h"
-#include "cbase.h"
-
-class CBaseUIContainerBase : public CBaseUIElement {
-   public:
-    CBaseUIContainerBase(UString name = "");
-    virtual ~CBaseUIContainerBase();
-
-    ELEMENT_BODY(CBaseUIContainerBase);
-
-    virtual CBaseUIContainerBase *addElement(CBaseUIElement *element, bool back = false);
-    virtual CBaseUIContainerBase *addElement(std::shared_ptr<CBaseUIElement> element, bool back = false);
-    virtual CBaseUIContainerBase *insertElement(CBaseUIElement *element, CBaseUIElement *index, bool back = false);
-    virtual CBaseUIContainerBase *insertElement(std::shared_ptr<CBaseUIElement> element, CBaseUIElement *index,
-                                                bool back = false);
-    virtual CBaseUIContainerBase *insertElement(CBaseUIElement *element, std::shared_ptr<CBaseUIElement> index,
-                                                bool back = false);
-    virtual CBaseUIContainerBase *insertElement(std::shared_ptr<CBaseUIElement> element,
-                                                std::shared_ptr<CBaseUIElement> index, bool back = false);
-
-    virtual void removeElement(CBaseUIElement *element);
-    virtual void removeElement(std::shared_ptr<CBaseUIElement> element);
-
-    virtual CBaseUIContainerBase *setClipping(bool clipping) {
-        m_bClipping = clipping;
-        return this;
-    }
-
-    CBaseUIElement *getElementByName(UString name, bool searchNestedContainers = false);
-    std::shared_ptr<CBaseUIElement> getElementSharedByName(UString name, bool searchNestedContainers = false);
-    std::vector<CBaseUIElement *> getAllElements();
-    inline std::vector<std::shared_ptr<CBaseUIElement>> getAllElementsShared() { return m_vElements; }
-    inline std::vector<std::shared_ptr<CBaseUIElement>> *getAllElementsReference() { return &m_vElements; }
-
-    virtual void draw(Graphics *g);
-    virtual void drawDebug(Graphics *g, Color color = COLOR(255, 255, 0, 0)) { ; }
-    virtual void mouse_update(bool *propagate_clicks);
-
-    virtual void empty();
-
-   protected:
-    // events
-    virtual void updateElement(CBaseUIElement *element) { ; }
-
-    bool m_bClipping;
-    std::vector<std::shared_ptr<CBaseUIElement>> m_vElements;
-};
-
-#endif /* GUI_CBASEUICONTAINERBASE_H_ */

+ 13 - 41
src/GUI/CBaseUIElement.cpp

@@ -1,47 +1,9 @@
-//================ Copyright (c) 2013, PG, All rights reserved. =================//
-//
-// Purpose:		the base class for all UI Elements
-//
-// $NoKeywords: $buie
-//===============================================================================//
-
 #include "CBaseUIElement.h"
 
 #include "Engine.h"
 #include "Mouse.h"
-
-CBaseUIElement::CBaseUIElement(float xPos, float yPos, float xSize, float ySize, UString name) {
-    // pos, size, name
-    m_vPos.x = xPos;
-    m_vPos.y = yPos;
-    m_vmPos.x = m_vPos.x;
-    m_vmPos.y = m_vPos.y;
-    m_vSize.x = xSize;
-    m_vSize.y = ySize;
-    m_vmSize.x = m_vSize.x;
-    m_vmSize.y = m_vSize.y;
-    m_vAnchor.x = 0;
-    m_vAnchor.y = 0;
-    m_sName = name;
-    m_parent = NULL;
-
-    // attributes
-    m_bVisible = true;
-    m_bActive = false;
-    m_bBusy = false;
-    m_bEnabled = true;
-
-    m_bKeepActive = false;
-    m_bDrawManually = false;
-    m_bPositionManually = false;
-    m_bMouseInside = false;
-
-    // container options
-    m_bScaleByHeightOnly = false;
-
-    m_bMouseInsideCheck = false;
-    m_bMouseUpCheck = false;
-}
+#include "Osu.h"
+#include "TooltipOverlay.h"
 
 void CBaseUIElement::mouse_update(bool *propagate_clicks) {
     // check if mouse is inside element
@@ -58,7 +20,17 @@ void CBaseUIElement::mouse_update(bool *propagate_clicks) {
         }
     }
 
-    if(!m_bVisible || !m_bEnabled) return;
+    if(!m_bVisible) return;
+
+    if(!m_bEnabled) {
+        if(m_bMouseInside && disabled_reason != NULL) {
+            osu->getTooltipOverlay()->begin();
+            osu->getTooltipOverlay()->addLine(disabled_reason);
+            osu->getTooltipOverlay()->end();
+        }
+
+        return;
+    }
 
     if(engine->getMouse()->isLeftDown() && *propagate_clicks) {
         m_bMouseUpCheck = true;

+ 125 - 399
src/GUI/CBaseUIElement.h

@@ -1,387 +1,27 @@
 #pragma once
+#include "KeyboardListener.h"
 #include "cbase.h"
 
-#define ELEMENT_BODY(T)                                          \
-    virtual T *setPos(float xPos, float yPos) {                  \
-        if(m_vPos.x != xPos || m_vPos.y != yPos) {               \
-            m_vPos.x = xPos - m_vSize.x * m_vAnchor.x;           \
-            m_vPos.y = yPos - m_vSize.y * m_vAnchor.y;           \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setPosX(float xPos) {                             \
-        if(m_vPos.x != xPos) {                                   \
-            m_vPos.x = xPos - m_vSize.x * m_vAnchor.x;           \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setPosY(float yPos) {                             \
-        if(m_vPos.y != yPos) {                                   \
-            m_vPos.y = yPos - m_vSize.y * m_vAnchor.y;           \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setPos(Vector2 position) {                        \
-        if(m_vPos != position) {                                 \
-            m_vPos = position - m_vSize * m_vAnchor;             \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-                                                                 \
-    virtual T *setPosAbsolute(float xPos, float yPos) {          \
-        if(m_vPos.x != xPos || m_vPos.y != yPos) {               \
-            m_vPos.x = xPos;                                     \
-            m_vPos.y = yPos;                                     \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setPosAbsoluteX(float xPos) {                     \
-        if(m_vPos.x != xPos) {                                   \
-            m_vPos.x = xPos;                                     \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setPosAbsoluteY(float yPos) {                     \
-        if(m_vPos.y != yPos) {                                   \
-            m_vPos.y = yPos;                                     \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setPosAbsolute(Vector2 position) {                \
-        if(m_vPos != position) {                                 \
-            m_vPos = position;                                   \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-                                                                 \
-    virtual T *setRelPos(float xPos, float yPos) {               \
-        if(m_vmPos.x != xPos || m_vmPos.y != yPos) {             \
-            m_vmPos.x = xPos - m_vSize.x * m_vAnchor.x;          \
-            m_vmPos.y = yPos - m_vSize.y * m_vAnchor.y;          \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setRelPosX(float xPos) {                          \
-        if(m_vmPos.x != xPos) {                                  \
-            m_vmPos.x = xPos - m_vSize.x * m_vAnchor.x;          \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setRelPosY(float yPos) {                          \
-        if(m_vmPos.y != yPos) {                                  \
-            m_vmPos.y = yPos - m_vSize.x * m_vAnchor.y;          \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setRelPos(Vector2 position) {                     \
-        if(m_vmPos != position) {                                \
-            m_vmPos = position - m_vSize * m_vAnchor;            \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-                                                                 \
-    virtual T *setRelPosAbsolute(float xPos, float yPos) {       \
-        if(m_vmPos.x != xPos || m_vmPos.y != yPos) {             \
-            m_vmPos.x = xPos;                                    \
-            m_vmPos.y = yPos;                                    \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setRelPosAbsoluteX(float xPos) {                  \
-        if(m_vmPos.x != xPos) {                                  \
-            m_vmPos.x = xPos;                                    \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setRelPosAbsoluteY(float yPos) {                  \
-        if(m_vmPos.y != yPos) {                                  \
-            m_vmPos.y = yPos;                                    \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setRelPosAbsolute(Vector2 position) {             \
-        if(m_vmPos != position) {                                \
-            m_vmPos = position;                                  \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-                                                                 \
-    virtual T *setSize(float xSize, float ySize) {               \
-        if(m_vSize.x != xSize || m_vSize.y != ySize) {           \
-            m_vPos.x += (m_vSize.x - xSize) * m_vAnchor.x;       \
-            m_vPos.y += (m_vSize.y - ySize) * m_vAnchor.y;       \
-            m_vSize.x = xSize;                                   \
-            m_vSize.y = ySize;                                   \
-            onResized();                                         \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setSizeX(float xSize) {                           \
-        if(m_vSize.x != xSize) {                                 \
-            m_vPos.x += (m_vSize.x - xSize) * m_vAnchor.x;       \
-            m_vSize.x = xSize;                                   \
-            onResized();                                         \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setSizeY(float ySize) {                           \
-        if(m_vSize.y != ySize) {                                 \
-            m_vPos.y += (m_vSize.y - ySize) * m_vAnchor.y;       \
-            m_vSize.y = ySize;                                   \
-            onResized();                                         \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setSize(Vector2 size) {                           \
-        if(m_vSize != size) {                                    \
-            m_vPos += (m_vSize - size) * m_vAnchor;              \
-            m_vSize = size;                                      \
-            onResized();                                         \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-                                                                 \
-    virtual T *setSizeAbsolute(float xSize, float ySize) {       \
-        if(m_vSize.x != xSize || m_vSize.y != ySize) {           \
-            m_vSize.x = xSize;                                   \
-            m_vSize.y = ySize;                                   \
-            onResized();                                         \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setSizeAbsoluteX(float xSize) {                   \
-        if(m_vSize.x != xSize) {                                 \
-            m_vSize.x = xSize;                                   \
-            onResized();                                         \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setSizeAbsoluteY(float ySize) {                   \
-        if(m_vSize.y != ySize) {                                 \
-            m_vSize.y = ySize;                                   \
-            onResized();                                         \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setSizeAbsolute(Vector2 size) {                   \
-        if(m_vSize != size) {                                    \
-            m_vSize = size;                                      \
-            onResized();                                         \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-                                                                 \
-    virtual T *setRelSize(float xSize, float ySize) {            \
-        if(m_vmSize.x != xSize || m_vmSize.y != ySize) {         \
-            m_vmPos.x += (m_vmSize.x - xSize) * m_vAnchor.x;     \
-            m_vmPos.y += (m_vmSize.y - ySize) * m_vAnchor.y;     \
-            m_vmSize.x = xSize;                                  \
-            m_vmSize.y = ySize;                                  \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setRelSizeX(float xSize) {                        \
-        if(m_vmSize.x != xSize) {                                \
-            m_vmPos.x += (m_vmSize.x - xSize) * m_vAnchor.x;     \
-            m_vmSize.x = xSize;                                  \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setRelSizeY(float ySize) {                        \
-        if(m_vmSize.y != ySize) {                                \
-            m_vmPos.y += (m_vmSize.y - ySize) * m_vAnchor.y;     \
-            m_vmSize.y = ySize;                                  \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setRelSize(Vector2 size) {                        \
-        if(m_vmSize != size) {                                   \
-            m_vmPos += (m_vmSize - size) * m_vAnchor;            \
-            m_vmSize = size;                                     \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-                                                                 \
-    virtual T *setRelSizeAbsolute(float xSize, float ySize) {    \
-        if(m_vmSize.x != xSize || m_vmSize.y != ySize) {         \
-            m_vmSize.x = xSize;                                  \
-            m_vmSize.y = ySize;                                  \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setRelSizeAbsoluteX(float xSize) {                \
-        if(m_vmSize.x != xSize) {                                \
-            m_vmSize.x = xSize;                                  \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setRelSizeAbsoluteY(float ySize) {                \
-        if(m_vmSize.y != ySize) {                                \
-            m_vmSize.y = ySize;                                  \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setRelSizeAbsolute(Vector2 size) {                \
-        if(m_vmSize != size) {                                   \
-            m_vmSize = size;                                     \
-            updateLayout();                                      \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-                                                                 \
-    virtual T *setAnchor(float xAnchor, float yAnchor) {         \
-        if(m_vAnchor.x != xAnchor || m_vAnchor.y != yAnchor) {   \
-            m_vmPos.x -= m_vmSize.x * (xAnchor - m_vAnchor.x);   \
-            m_vmPos.y -= m_vmSize.y * (yAnchor - m_vAnchor.y);   \
-            m_vPos.x -= m_vSize.x * (xAnchor - m_vAnchor.x);     \
-            m_vPos.y -= m_vSize.y * (yAnchor - m_vAnchor.y);     \
-            m_vAnchor.x = xAnchor;                               \
-            m_vAnchor.y = yAnchor;                               \
-            if(m_parent != NULL) updateLayout();                 \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setAnchorX(float xAnchor) {                       \
-        if(m_vAnchor.x != xAnchor) {                             \
-            m_vmPos.x -= m_vmSize.x * (xAnchor - m_vAnchor.x);   \
-            m_vPos.x -= m_vSize.x * (xAnchor - m_vAnchor.x);     \
-            m_vAnchor.x = xAnchor;                               \
-            if(m_parent != NULL) updateLayout();                 \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setAnchorY(float yAnchor) {                       \
-        if(m_vAnchor.y != yAnchor) {                             \
-            m_vmPos.y -= m_vmSize.y * (yAnchor - m_vAnchor.y);   \
-            m_vPos.y -= m_vSize.y * (yAnchor - m_vAnchor.y);     \
-            m_vAnchor.y = yAnchor;                               \
-            if(m_parent != NULL) updateLayout();                 \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setAnchor(Vector2 anchor) {                       \
-        if(m_vAnchor != anchor) {                                \
-            m_vmPos -= m_vmSize * (anchor - m_vAnchor);          \
-            m_vPos -= m_vSize * (anchor - m_vAnchor);            \
-            m_vAnchor = anchor;                                  \
-            if(m_parent != NULL) updateLayout();                 \
-            onMoved();                                           \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-                                                                 \
-    virtual T *setAnchorAbsolute(float xAnchor, float yAnchor) { \
-        if(m_vAnchor.x != xAnchor || m_vAnchor.y != yAnchor) {   \
-            m_vAnchor.x = xAnchor, m_vAnchor.y = yAnchor;        \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setAnchorAbsoluteX(float xAnchor) {               \
-        if(m_vAnchor.x != xAnchor) {                             \
-            m_vAnchor.x = xAnchor;                               \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setAnchorAbsoluteY(float yAnchor) {               \
-        if(m_vAnchor.y != yAnchor) {                             \
-            m_vAnchor.y = yAnchor;                               \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setAnchorAbsolute(Vector2 anchor) {               \
-        if(m_vAnchor != anchor) {                                \
-            m_vAnchor = anchor;                                  \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-                                                                 \
-    virtual T *setVisible(bool visible) {                        \
-        m_bVisible = visible;                                    \
-        return this;                                             \
-    }                                                            \
-    virtual T *setActive(bool active) {                          \
-        m_bActive = active;                                      \
-        return this;                                             \
-    }                                                            \
-    virtual T *setKeepActive(bool keepActive) {                  \
-        m_bKeepActive = keepActive;                              \
-        return this;                                             \
-    }                                                            \
-    virtual T *setDrawManually(bool drawManually) {              \
-        m_bDrawManually = drawManually;                          \
-        return this;                                             \
-    }                                                            \
-    virtual T *setPositionManually(bool positionManually) {      \
-        m_bPositionManually = positionManually;                  \
-        return this;                                             \
-    }                                                            \
-    virtual T *setEnabled(bool enabled) {                        \
-        if(enabled != m_bEnabled) {                              \
-            m_bEnabled = enabled;                                \
-            if(m_bEnabled) {                                     \
-                onEnabled();                                     \
-            } else {                                             \
-                onDisabled();                                    \
-            }                                                    \
-        }                                                        \
-        return this;                                             \
-    }                                                            \
-    virtual T *setBusy(bool busy) {                              \
-        m_bBusy = busy;                                          \
-        return this;                                             \
-    }                                                            \
-    virtual T *setName(UString name) {                           \
-        m_sName = name;                                          \
-        return this;                                             \
-    }                                                            \
-    virtual T *setParent(CBaseUIElement *parent) {               \
-        m_parent = parent;                                       \
-        return this;                                             \
-    }                                                            \
-    virtual T *setScaleByHeightOnly(bool scaleByHeightOnly) {    \
-        m_bScaleByHeightOnly = scaleByHeightOnly;                \
-        return this;                                             \
-    }
-
-#include "KeyboardListener.h"
+// Guidelines for avoiding hair pulling:
+// - Don't use m_vmSize
+// - When an element is standalone, use getPos/setPos
+// - In a container or in a scrollview, use getRelPos/setRelPos and call update_pos() on the container
 
 class CBaseUIElement : public KeyboardListener {
    public:
-    CBaseUIElement(float xPos = 0, float yPos = 0, float xSize = 0, float ySize = 0, UString name = "");
+    CBaseUIElement(float xPos = 0, float yPos = 0, float xSize = 0, float ySize = 0, UString name = "") {
+        m_vPos.x = xPos;
+        m_vPos.y = yPos;
+        m_vmPos.x = m_vPos.x;
+        m_vmPos.y = m_vPos.y;
+        m_vSize.x = xSize;
+        m_vSize.y = ySize;
+        m_vmSize.x = m_vSize.x;
+        m_vmSize.y = m_vSize.y;
+        m_sName = name;
+    }
     virtual ~CBaseUIElement() { ; }
 
-    ELEMENT_BODY(CBaseUIElement)
-
     // main
     virtual void draw(Graphics *g) = 0;
     virtual void mouse_update(bool *propagate_clicks);
@@ -398,17 +38,111 @@ class CBaseUIElement : public KeyboardListener {
     inline UString getName() const { return m_sName; }
     inline const Vector2 &getRelPos() const { return m_vmPos; }
     inline const Vector2 &getRelSize() const { return m_vmSize; }
-    inline const Vector2 &getAnchor() const { return m_vAnchor; }
-    inline CBaseUIElement *getParent() const { return m_parent; }
 
     virtual bool isActive() { return m_bActive || isBusy(); }
     virtual bool isVisible() { return m_bVisible; }
     virtual bool isEnabled() { return m_bEnabled; }
     virtual bool isBusy() { return m_bBusy && isVisible(); }
-    virtual bool isDrawnManually() { return m_bDrawManually; }
-    virtual bool isPositionedManually() { return m_bPositionManually; }
     virtual bool isMouseInside() { return m_bMouseInside && isVisible(); }
-    virtual bool isScaledByHeightOnly() { return m_bScaleByHeightOnly; }
+
+    virtual CBaseUIElement *setPos(float xPos, float yPos) {
+        if(m_vPos.x != xPos || m_vPos.y != yPos) {
+            m_vPos.x = xPos;
+            m_vPos.y = yPos;
+            onMoved();
+        }
+        return this;
+    }
+    virtual CBaseUIElement *setPosX(float xPos) {
+        if(m_vPos.x != xPos) {
+            m_vPos.x = xPos;
+            onMoved();
+        }
+        return this;
+    }
+    virtual CBaseUIElement *setPosY(float yPos) {
+        if(m_vPos.y != yPos) {
+            m_vPos.y = yPos;
+            onMoved();
+        }
+        return this;
+    }
+    virtual CBaseUIElement *setPos(Vector2 position) { return setPos(position.x, position.y); }
+
+    virtual CBaseUIElement *setRelPos(float xPos, float yPos) {
+        m_vmPos.x = xPos;
+        m_vmPos.y = yPos;
+        return this;
+    }
+    virtual CBaseUIElement *setRelPosX(float xPos) {
+        m_vmPos.x = xPos;
+        return this;
+    }
+    virtual CBaseUIElement *setRelPosY(float yPos) {
+        m_vmPos.y = yPos;
+        return this;
+    }
+    virtual CBaseUIElement *setRelPos(Vector2 position) { return setRelPos(position.x, position.y); }
+
+    virtual CBaseUIElement *setSize(float xSize, float ySize) {
+        if(m_vSize.x != xSize || m_vSize.y != ySize) {
+            m_vSize.x = xSize;
+            m_vSize.y = ySize;
+            onResized();
+            onMoved();
+        }
+        return this;
+    }
+    virtual CBaseUIElement *setSizeX(float xSize) {
+        if(m_vSize.x != xSize) {
+            m_vSize.x = xSize;
+            onResized();
+            onMoved();
+        }
+        return this;
+    }
+    virtual CBaseUIElement *setSizeY(float ySize) {
+        if(m_vSize.y != ySize) {
+            m_vSize.y = ySize;
+            onResized();
+            onMoved();
+        }
+        return this;
+    }
+    virtual CBaseUIElement *setSize(Vector2 size) { return setSize(size.x, size.y); }
+
+    virtual CBaseUIElement *setVisible(bool visible) {
+        m_bVisible = visible;
+        return this;
+    }
+    virtual CBaseUIElement *setActive(bool active) {
+        m_bActive = active;
+        return this;
+    }
+    virtual CBaseUIElement *setKeepActive(bool keepActive) {
+        m_bKeepActive = keepActive;
+        return this;
+    }
+    virtual CBaseUIElement *setEnabled(bool enabled, const char *reason = NULL) {
+        if(enabled != m_bEnabled) {
+            m_bEnabled = enabled;
+            if(m_bEnabled) {
+                onEnabled();
+            } else {
+                disabled_reason = reason;
+                onDisabled();
+            }
+        }
+        return this;
+    }
+    virtual CBaseUIElement *setBusy(bool busy) {
+        m_bBusy = busy;
+        return this;
+    }
+    virtual CBaseUIElement *setName(UString name) {
+        m_sName = name;
+        return this;
+    }
 
     // actions
     void stealFocus() {
@@ -416,9 +150,6 @@ class CBaseUIElement : public KeyboardListener {
         m_bActive = false;
         onFocusStolen();
     }
-    void updateLayout() {
-        if(m_parent != NULL) m_parent->updateLayout();
-    }
 
    protected:
     // events
@@ -438,30 +169,25 @@ class CBaseUIElement : public KeyboardListener {
 
     // vars
     UString m_sName;
-    CBaseUIElement *m_parent;
 
     // attributes
-    bool m_bVisible;
-    bool m_bActive;  // we are doing something, e.g. textbox is blinking and ready to receive input
-    bool m_bBusy;    // we demand the focus to be kept on us, e.g. click-drag scrolling in a scrollview
-    bool m_bEnabled;
-
-    bool m_bKeepActive;  // once clicked, don't lose m_bActive, we have to manually release it (e.g. textbox)
-    bool m_bDrawManually;
-    bool m_bPositionManually;
-    bool m_bMouseInside;
+    bool m_bVisible = true;
+    bool m_bActive = false;  // we are doing something, e.g. textbox is blinking and ready to receive input
+    bool m_bBusy = false;    // we demand the focus to be kept on us, e.g. click-drag scrolling in a scrollview
+    bool m_bEnabled = true;
 
-    // container options
-    bool m_bScaleByHeightOnly;
+    bool m_bKeepActive = false;  // once clicked, don't lose m_bActive, we have to manually release it (e.g. textbox)
+    bool m_bMouseInside = false;
 
     // position and size
     Vector2 m_vPos;
     Vector2 m_vmPos;
     Vector2 m_vSize;
     Vector2 m_vmSize;
-    Vector2 m_vAnchor;  // the point of transformation
+
+    const char *disabled_reason = NULL;
 
    private:
-    bool m_bMouseInsideCheck;
-    bool m_bMouseUpCheck;
+    bool m_bMouseInsideCheck = false;
+    bool m_bMouseUpCheck = false;
 };

+ 1 - 14
src/GUI/CBaseUIImage.h

@@ -1,13 +1,4 @@
-//================ Copyright (c) 2014, PG, All rights reserved. =================//
-//
-// Purpose:		a simple image class
-//
-// $NoKeywords: $
-//===============================================================================//
-
-#ifndef CBASEUIIMAGE_H
-#define CBASEUIIMAGE_H
-
+#pragma once
 #include "CBaseUIElement.h"
 
 class CBaseUIImage : public CBaseUIElement {
@@ -16,8 +7,6 @@ class CBaseUIImage : public CBaseUIElement {
                  UString name = "");
     virtual ~CBaseUIImage() { ; }
 
-    ELEMENT_BODY(CBaseUIImage)
-
     virtual void draw(Graphics *g);
 
     void setImage(Image *img);
@@ -86,5 +75,3 @@ class CBaseUIImage : public CBaseUIElement {
     float m_fRot;
     Vector2 m_vScale;
 };
-
-#endif

+ 1 - 14
src/GUI/CBaseUIImageButton.h

@@ -1,13 +1,4 @@
-//================ Copyright (c) 2015, PG, All rights reserved. =================//
-//
-// Purpose:		a simple image button
-//
-// $NoKeywords: $
-//===============================================================================//
-
-#ifndef CBASEUIIMAGEBUTTON_H
-#define CBASEUIIMAGEBUTTON_H
-
+#pragma once
 #include "CBaseUIButton.h"
 
 class CBaseUIImageButton : public CBaseUIButton {
@@ -16,8 +7,6 @@ class CBaseUIImageButton : public CBaseUIButton {
                        float ySize = 0, UString name = "");
     virtual ~CBaseUIImageButton() { ; }
 
-    ELEMENT_BODY(CBaseUIImageButton)
-
     virtual void draw(Graphics *g);
 
     virtual void onResized();
@@ -52,5 +41,3 @@ class CBaseUIImageButton : public CBaseUIButton {
     bool m_bScaleToFit;
     bool m_bKeepAspectRatio;
 };
-
-#endif

+ 0 - 7
src/GUI/CBaseUILabel.cpp

@@ -1,10 +1,3 @@
-//================ Copyright (c) 2014, PG, All rights reserved. =================//
-//
-// Purpose:		a simple label
-//
-// $NoKeywords: $
-//===============================================================================//
-
 #include "CBaseUILabel.h"
 
 #include "Engine.h"

+ 6 - 14
src/GUI/CBaseUILabel.h

@@ -1,13 +1,4 @@
-//================ Copyright (c) 2014, PG, All rights reserved. =================//
-//
-// Purpose:		a simple label
-//
-// $NoKeywords: $
-//===============================================================================//
-
-#ifndef CBASEUILABEL_H
-#define CBASEUILABEL_H
-
+#pragma once
 #include "CBaseUIElement.h"
 
 class CBaseUILabel : public CBaseUIElement {
@@ -19,11 +10,14 @@ class CBaseUILabel : public CBaseUIElement {
                  UString text = "");
     virtual ~CBaseUILabel() { ; }
 
-    ELEMENT_BODY(CBaseUILabel)
-
     virtual void draw(Graphics *g);
     virtual void mouse_update(bool *propagate_clicks);
 
+    // cancer
+    void setRelSizeX(float x) {
+        m_vmSize.x = x;
+    }
+
     // set
     CBaseUILabel *setDrawFrame(bool drawFrame) {
         m_bDrawFrame = drawFrame;
@@ -103,5 +97,3 @@ class CBaseUILabel : public CBaseUIElement {
 
     TEXT_JUSTIFICATION m_textJustification;
 };
-
-#endif

+ 2 - 4
src/GUI/CBaseUIScrollView.h

@@ -10,8 +10,6 @@ class CBaseUIScrollView : public CBaseUIElement {
 
     void clear();
 
-    ELEMENT_BODY(CBaseUIScrollView)
-
     virtual void draw(Graphics *g);
     virtual void mouse_update(bool *propagate_clicks);
 
@@ -94,8 +92,8 @@ class CBaseUIScrollView : public CBaseUIElement {
 
     // get
     inline CBaseUIContainer *getContainer() const { return m_container; }
-    inline float getScrollPosY() const { return m_vScrollPos.y; }
-    inline float getScrollPosX() const { return m_vScrollPos.x; }
+    inline float getRelPosY() const { return m_vScrollPos.y; }
+    inline float getRelPosX() const { return m_vScrollPos.x; }
     inline Vector2 getScrollSize() const { return m_vScrollSize; }
     inline Vector2 getVelocity() const { return (m_vScrollPos - m_vVelocity); }
 

+ 0 - 2
src/GUI/CBaseUISlider.h

@@ -10,8 +10,6 @@ class CBaseUISlider : public CBaseUIElement {
     CBaseUISlider(float xPos = 0, float yPos = 0, float xSize = 0, float ySize = 0, UString name = "");
     virtual ~CBaseUISlider() { ; }
 
-    ELEMENT_BODY(CBaseUISlider)
-
     virtual void draw(Graphics *g);
     virtual void mouse_update(bool *propagate_clicks);
 

+ 2 - 2
src/GUI/CBaseUITextField.cpp

@@ -32,8 +32,8 @@ void CBaseUITextField::onResized() {
     CBaseUIScrollView::onResized();
     m_textObject->setParentSize(m_vSize);
     // m_textObject->setSize(m_vSize);
-    scrollToX(getScrollPosX());
-    scrollToY(getScrollPosY());
+    scrollToX(getRelPosX());
+    scrollToY(getRelPosY());
     setScrollSizeToContent(0);
 }
 

+ 1 - 16
src/GUI/CBaseUITextField.h

@@ -1,15 +1,4 @@
-//================ Copyright (c) 2014, PG, All rights reserved. =================//
-//
-// Purpose:		a not so simple textfield
-//
-// $NoKeywords: $
-//===============================================================================//
-
-#ifndef CBASEUITEXTFIELD_H
-#define CBASEUITEXTFIELD_H
-
-// TODO: finish this
-
+#pragma once
 #include "CBaseUIScrollView.h"
 
 class CBaseUITextField : public CBaseUIScrollView {
@@ -18,8 +7,6 @@ class CBaseUITextField : public CBaseUIScrollView {
                      UString text = "");
     virtual ~CBaseUITextField() { ; }
 
-    ELEMENT_BODY(CBaseUITextField)
-
     virtual void draw(Graphics *g);
 
     CBaseUITextField *setFont(McFont *font) {
@@ -79,5 +66,3 @@ class CBaseUITextField : public CBaseUIScrollView {
 
     TextObject *m_textObject;
 };
-
-#endif

+ 0 - 2
src/GUI/CBaseUITextbox.h

@@ -8,8 +8,6 @@ class CBaseUITextbox : public CBaseUIElement {
     CBaseUITextbox(float xPos = 0.0f, float yPos = 0.0f, float xSize = 0.0f, float ySize = 0.0f, UString name = "");
     virtual ~CBaseUITextbox() { ; }
 
-    ELEMENT_BODY(CBaseUITextbox)
-
     virtual void draw(Graphics *g);
     virtual void mouse_update(bool *propagate_clicks);
     virtual void onFocusStolen();

+ 1 - 14
src/GUI/CBaseUIWindow.h

@@ -1,13 +1,4 @@
-//================ Copyright (c) 2014, PG, All rights reserved. =================//
-//
-// Purpose:		base class for windows
-//
-// $NoKeywords: $
-//===============================================================================//
-
-#ifndef CBASEUIWINDOW_H
-#define CBASEUIWINDOW_H
-
+#pragma once
 #include "CBaseUIElement.h"
 
 class CBaseUIButton;
@@ -21,8 +12,6 @@ class CBaseUIWindow : public CBaseUIElement {
     CBaseUIWindow(float xPos = 0, float yPos = 0, float xSize = 0, float ySize = 0, UString name = "");
     ~CBaseUIWindow();
 
-    ELEMENT_BODY(CBaseUIWindow)
-
     virtual void draw(Graphics *g);
     virtual void drawCustomContent(Graphics *g) { (void)g; }
     virtual void mouse_update(bool *propagate_clicks);
@@ -180,5 +169,3 @@ class CBaseUIWindow : public CBaseUIElement {
     RenderTarget *m_rt;
     CBaseUIBoxShadow *m_shadow;
 };
-
-#endif

+ 2 - 2
src/GUI/Windows/Console.cpp

@@ -265,8 +265,8 @@ void Console::onResized() {
     m_textbox->setSize(m_vSize.x - 2 * CONSOLE_BORDER, m_textbox->getSize().y);
     m_textbox->setRelPosY(m_log->getRelPos().y + m_log->getSize().y + CONSOLE_BORDER + 1);
 
-    m_log->scrollToY(m_log->getScrollPosY());
-    // m_newLog->scrollY(m_newLog->getScrollPosY());
+    m_log->scrollToY(m_log->getRelPosY());
+    // m_newLog->scrollY(m_newLog->getRelPosY());
 }
 
 //***********************//

+ 2 - 9
src/GUI/Windows/VinylScratcher/VSMusicBrowser.cpp

@@ -1,10 +1,3 @@
-//================ Copyright (c) 2014, PG, All rights reserved. =================//
-//
-// Purpose:		a simple drive and file selector
-//
-// $NoKeywords: $
-//===============================================================================//
-
 #include "VSMusicBrowser.h"
 
 #include "AnimationHandler.h"
@@ -625,12 +618,12 @@ void VSMusicBrowser::onMoved() { m_mainContainer->setPos(m_vPos); }
 void VSMusicBrowser::onResized() {
     for(size_t i = 0; i < m_columns.size(); i++) {
         m_columns[i].view->setSizeY(m_vSize.y);
-        m_columns[i].view->scrollToY(m_columns[i].view->getScrollPosY());
+        m_columns[i].view->scrollToY(m_columns[i].view->getRelPosY());
     }
 
     m_mainContainer->setSize(m_vSize);
     m_mainContainer->setScrollSizeToContent(0);
-    m_mainContainer->scrollToX(m_mainContainer->getScrollPosX());
+    m_mainContainer->scrollToX(m_mainContainer->getRelPosX());
 }
 
 void VSMusicBrowser::onFocusStolen() {