/* This file is part of RetroFE. * * RetroFE is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * RetroFE is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RetroFE. If not, see . */ #include "RetroFE.h" #include "Collection/CollectionInfoBuilder.h" #include "Collection/CollectionInfo.h" #include "Database/Configuration.h" #include "Collection/Item.h" #include "Execute/Launcher.h" #include "Utility/Log.h" #include "Collection/MenuParser.h" #include "SDL.h" #include "Control/UserInput.h" #include "Graphics/PageBuilder.h" #include "Graphics/Page.h" #include "Graphics/Component/ScrollingList.h" #include "Video/VideoFactory.h" #include #include #include #ifdef WIN32 #include #include #include #endif RetroFE::RetroFE(Configuration &c) : Initialized(false) , InitializeThread(NULL) , Config(c) , Db(NULL) , Input(Config) , KeyInputDisable(0) , CurrentTime(0) { } RetroFE::~RetroFE() { DeInitialize(); } void RetroFE::Render() { SDL_LockMutex(SDL::GetMutex()); SDL_SetRenderDrawColor(SDL::GetRenderer(), 0x0, 0x0, 0x00, 0xFF); SDL_RenderClear(SDL::GetRenderer()); if(PageChain.size() > 0) { Page *page = PageChain.back(); if(page) { page->Draw(); } } SDL_RenderPresent(SDL::GetRenderer()); SDL_UnlockMutex(SDL::GetMutex()); } int RetroFE::Initialize(void *context) { int retVal = 0; RetroFE *instance = static_cast(context); Logger::Write(Logger::ZONE_INFO, "RetroFE", "Initializing"); bool videoEnable = true; int videoLoop = 0; if(!instance->Input.Initialize()) return -1; instance->Db = new DB(Configuration::GetAbsolutePath() + "/cache.db"); if(!instance->Db->Initialize()) { Logger::Write(Logger::ZONE_ERROR, "RetroFE", "Could not initialize database"); return -1; } instance->Config.GetProperty("videoEnable", videoEnable); instance->Config.GetProperty("videoLoop", videoLoop); VideoFactory::SetEnabled(videoEnable); VideoFactory::SetNumLoops(videoLoop); instance->Initialized = true; return 0; } void RetroFE::LaunchEnter() { if(PageChain.size() > 0) { Page *p = PageChain.back(); p->LaunchEnter(); } SDL_SetWindowGrab(SDL::GetWindow(), SDL_FALSE); } void RetroFE::LaunchExit() { SDL_RestoreWindow(SDL::GetWindow()); SDL_SetWindowGrab(SDL::GetWindow(), SDL_TRUE); if(PageChain.size() > 0) { Page *p = PageChain.back(); p->LaunchExit(); } } void RetroFE::FreeGraphicsMemory() { if(PageChain.size() > 0) { Page *p = PageChain.back(); p->FreeGraphicsMemory(); } FC.DeInitialize(); SDL::DeInitialize(); } void RetroFE::AllocateGraphicsMemory() { SDL::Initialize(Config); FC.Initialize(); if(PageChain.size() > 0) { Page *p = PageChain.back(); p->AllocateGraphicsMemory(); p->Start(); } } bool RetroFE::DeInitialize() { bool retVal = true; FreeGraphicsMemory(); bool videoEnable = true; while(PageChain.size() > 0) { Page *page = PageChain.back(); delete page; PageChain.pop_back(); } if(Db) { delete Db; Db = NULL; } Initialized = false; //todo: handle video deallocation return retVal; } void RetroFE::Run() { if(!SDL::Initialize(Config)) return; FC.Initialize(); InitializeThread = SDL_CreateThread(Initialize, "RetroFEInit", (void *)this); if(!InitializeThread) { Logger::Write(Logger::ZONE_INFO, "RetroFE", "Could not initialize RetroFE"); return; } int attractModeTime = 0; std::string firstCollection = "Main"; bool running = true; Item *nextPageItem = NULL; bool adminMode = false; RETROFE_STATE state = RETROFE_NEW; Config.GetProperty("attractModeTime", attractModeTime); Config.GetProperty("firstCollection", firstCollection); Attract.SetIdleTime(static_cast(attractModeTime)); int initializeStatus = 0; // load the initial splash screen, unload it once it is complete Page * page = LoadSplashPage(); bool splashMode = true; Launcher l(*this, Config); while (running) { float lastTime = 0; float deltaTime = 0; page = PageChain.back(); if(!page) { Logger::Write(Logger::ZONE_WARNING, "RetroFE", "Could not load page"); running = false; break; } // todo: This could be transformed to use the state design pattern. switch(state) { case RETROFE_IDLE: if(page && !splashMode) { state = ProcessUserInput(page); } if(Initialized && splashMode) { SDL_WaitThread(InitializeThread, &initializeStatus); state = RETROFE_BACK_WAIT; page->Stop(); } break; case RETROFE_NEXT_PAGE_REQUEST: page->Stop(); state = RETROFE_NEXT_PAGE_WAIT; break; case RETROFE_NEXT_PAGE_WAIT: if(page->IsHidden()) { page = LoadPage(NextPageItem->GetName()); state = RETROFE_NEW; } break; case RETROFE_LAUNCH_REQUEST: l.Run(page->GetCollectionName(), NextPageItem); state = RETROFE_IDLE; break; case RETROFE_BACK_REQUEST: page->Stop(); state = RETROFE_BACK_WAIT; break; case RETROFE_BACK_WAIT: if(page->IsHidden()) { PageChain.pop_back(); delete page; page = (splashMode) ? LoadPage(firstCollection) : PageChain.back(); splashMode = false; CurrentTime = (float)SDL_GetTicks() / 1000; page->AllocateGraphicsMemory(); page->Start(); state = RETROFE_NEW; } break; case RETROFE_NEW: if(page->IsIdle()) { state = RETROFE_IDLE; } break; case RETROFE_QUIT_REQUEST: page->Stop(); state = RETROFE_QUIT; break; case RETROFE_QUIT: if(page->IsHidden()) { running = false; } break; } // the logic below could be done in a helper method if(running) { lastTime = CurrentTime; CurrentTime = (float)SDL_GetTicks() / 1000; if (CurrentTime < lastTime) { CurrentTime = lastTime; } deltaTime = CurrentTime - lastTime; double sleepTime = 1000.0/60.0 - deltaTime*1000; if(sleepTime > 0) { SDL_Delay(static_cast(sleepTime)); } if(page) { Attract.Update(deltaTime, *page); page->Update(deltaTime); } Render(); } } } bool RetroFE::Back(bool &exit) { bool canGoBack = false; bool exitOnBack = false; Config.GetProperty("exitOnFirstPageBack", exitOnBack); exit = false; if(PageChain.size() > 1) { Page *page = PageChain.back(); page->Stop(); canGoBack = true; } else if(PageChain.size() == 1 && exitOnBack) { Page *page = PageChain.back(); page->Stop(); exit = true; canGoBack = true; } return canGoBack; } RetroFE::RETROFE_STATE RetroFE::ProcessUserInput(Page *page) { SDL_Event e; bool exit = false; RETROFE_STATE state = RETROFE_IDLE; if (SDL_PollEvent(&e) == 0) return state; if(e.type == SDL_KEYDOWN || e.type == SDL_KEYUP) { const Uint8 *keys = SDL_GetKeyboardState(NULL); Attract.Reset(); if (keys[Input.GetScancode(UserInput::KeyCodePreviousItem)]) { page->SetScrolling(Page::ScrollDirectionBack); } if (keys[Input.GetScancode(UserInput::KeyCodeNextItem)]) { page->SetScrolling(Page::ScrollDirectionForward); } if (keys[Input.GetScancode(UserInput::KeyCodePageUp)]) { page->PageScroll(Page::ScrollDirectionBack); } if (keys[Input.GetScancode(UserInput::KeyCodePageDown)]) { page->PageScroll(Page::ScrollDirectionForward); } if (keys[Input.GetScancode(UserInput::KeyCodeAdminMode)]) { //todo: add admin mode support } if (keys[Input.GetScancode(UserInput::KeyCodeSelect)]) { NextPageItem = page->GetSelectedItem(); if(NextPageItem) { state = (NextPageItem->IsLeaf()) ? RETROFE_LAUNCH_REQUEST : RETROFE_NEXT_PAGE_REQUEST; } } if (keys[Input.GetScancode(UserInput::KeyCodeBack)]) { if(Back(exit)) { state = (exit) ? RETROFE_QUIT_REQUEST : RETROFE_BACK_REQUEST; } } if (keys[Input.GetScancode(UserInput::KeyCodeQuit)]) { state = RETROFE_QUIT_REQUEST; } if(!keys[Input.GetScancode(UserInput::KeyCodePreviousItem)] && !keys[Input.GetScancode(UserInput::KeyCodeNextItem)] && !keys[Input.GetScancode(UserInput::KeyCodePageUp)] && !keys[Input.GetScancode(UserInput::KeyCodePageDown)]) { page->SetScrolling(Page::ScrollDirectionIdle); } } return state; } void RetroFE::WaitToInitialize() { Logger::Write(Logger::ZONE_INFO, "RetroFE", "Loading splash screen"); PageBuilder pb("splash", "", Config, &FC); Page * page = pb.BuildPage(); while(!Initialized) { SDL_SetRenderDrawColor(SDL::GetRenderer(), 0x0, 0x0, 0x00, 0xFF); SDL_RenderClear(SDL::GetRenderer()); page->Draw(); SDL_RenderPresent(SDL::GetRenderer()); } int status = 0; delete page; SDL_WaitThread(InitializeThread, &status); } Page *RetroFE::LoadPage(std::string collectionName) { Logger::Write(Logger::ZONE_INFO, "RetroFE", "Creating page for collection " + collectionName); Page *page = NULL; Config.SetCurrentCollection(collectionName); CollectionInfo *collection = GetCollection(collectionName); std::string layoutName = GetLayout(collectionName); if(PageChain.size() > 0) { Page *oldPage = PageChain.back(); oldPage->FreeGraphicsMemory(); } PageBuilder pb(layoutName, collectionName, Config, &FC); page = pb.BuildPage(); if(!page) { Logger::Write(Logger::ZONE_ERROR, "RetroFE", "Could not create page for " + collectionName); } else { page->SetCollection(collection); page->Start(); PageChain.push_back(page); } return page; } Page *RetroFE::LoadSplashPage() { PageBuilder pb("Splash", "", Config, &FC); Page * page = pb.BuildPage(); // page->SetStatusText("foobar"); Config.SetCurrentCollection(""); page->SetCollection(new CollectionInfo("", "", "", "", "")); page->Start(); PageChain.push_back(page); return page; } CollectionInfo *RetroFE::GetCollection(std::string collectionName) { // the page will deallocate this once its done MenuParser mp; CollectionInfoBuilder cib(Config, *Db); CollectionInfo *collection = cib.BuildCollection(collectionName); mp.GetMenuItems(collection); if(collection->GetItems()->size() == 0) { Logger::Write(Logger::ZONE_WARNING, "RetroFE", "No list items found for collection " + collectionName); } return collection; } std::string RetroFE::GetLayout(std::string collectionName) { std::string layoutKeyName = "collections." + collectionName + ".layout"; std::string layoutName = "Default 16x9"; if(!Config.GetProperty(layoutKeyName, layoutName)) { Config.GetProperty("layout", layoutName); } return layoutName; }