colobot/colobot-base/ui/screen/screen_mod_list.cpp

583 lines
16 KiB
C++

/*
* This file is part of the Colobot: Gold Edition source code
* Copyright (C) 2001-2023, Daniel Roux, EPSITEC SA & TerranovaTeam
* http://epsitec.ch; http://colobot.info; http://github.com/colobot
*
* This program 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.
*
* This program 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 this program. If not, see http://gnu.org/licenses
*/
#include "ui/screen/screen_mod_list.h"
#include "common/config.h"
#include "app/app.h"
#include "app/modman.h"
#include "common/logger.h"
#include "common/restext.h"
#include "common/stringutils.h"
#include "common/resources/resourcemanager.h"
#include "common/system/system.h"
#include "level/robotmain.h"
#include "math/func.h"
#include "sound/sound.h"
#include "ui/controls/button.h"
#include "ui/controls/edit.h"
#include "ui/controls/interface.h"
#include "ui/controls/label.h"
#include "ui/controls/list.h"
#include "ui/controls/window.h"
#include <algorithm>
namespace Ui
{
CScreenModList::CScreenModList(CMainDialog* dialog, CModManager* modManager)
: m_dialog(dialog),
m_modManager(modManager)
{
}
void CScreenModList::CreateInterface()
{
CWindow* pw;
CEdit* pe;
CLabel* pl;
CButton* pb;
CList* pli;
glm::vec2 pos, ddim;
std::string name;
// Display the window
pos.x = 0.10f;
pos.y = 0.10f;
ddim.x = 0.80f;
ddim.y = 0.80f;
pw = m_interface->CreateWindows(pos, ddim, 12, EVENT_WINDOW5);
pw->SetClosable(true);
GetResource(RES_TEXT, RT_TITLE_MODS, name);
pw->SetName(name);
pos.x = 0.10f;
pos.y = 0.40f;
ddim.x = 0.50f;
ddim.y = 0.50f;
pw->CreateGroup(pos, ddim, 5, EVENT_INTERFACE_GLINTl); // orange corner
pos.x = 0.40f;
pos.y = 0.10f;
ddim.x = 0.50f;
ddim.y = 0.50f;
pw->CreateGroup(pos, ddim, 4, EVENT_INTERFACE_GLINTr); // blue corner
// Display the list of mods
pos.x = ox+sx*3;
pos.y = oy+sy*10.5f;
ddim.x = dim.x*7.5f;
ddim.y = dim.y*0.6f;
GetResource(RES_TEXT, RT_MOD_LIST, name);
pl = pw->CreateLabel(pos, ddim, 0, EVENT_LABEL11, name);
pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT);
pos.y = oy+sy*6.7f;
ddim.y = dim.y*4.6f;
ddim.x = dim.x*6.5f;
pli = pw->CreateList(pos, ddim, 0, EVENT_INTERFACE_MOD_LIST);
pli->SetState(STATE_SHADOW);
pli->SetState(STATE_EXTEND);
// Displays the mod details
pos.x = ox+sx*9.5f;
pos.y = oy+sy*10.5f;
ddim.x = dim.x*7.5f;
ddim.y = dim.y*0.6f;
GetResource(RES_TEXT, RT_MOD_DETAILS, name);
pl = pw->CreateLabel(pos, ddim, 0, EVENT_LABEL12, name);
pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT);
pos.y = oy+sy*6.7f;
ddim.y = dim.y*4.3f;
ddim.x = dim.x*6.5f;
pe = pw->CreateEdit(pos, ddim, 0, EVENT_INTERFACE_MOD_DETAILS);
pe->SetState(STATE_SHADOW);
pe->SetMaxChar(500);
pe->SetEditCap(false); // just to see
pe->SetHighlightCap(true);
pos = pli->GetPos();
ddim = pli->GetDim();
// Displays the mod summary
pos.x = ox+sx*3;
pos.y = oy+sy*5.4f;
ddim.x = dim.x*6.5f;
ddim.y = dim.y*0.6f;
GetResource(RES_TEXT, RT_MOD_SUMMARY, name);
pl = pw->CreateLabel(pos, ddim, 0, EVENT_LABEL13, name);
pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT);
pos.x = ox+sx*3;
pos.y = oy+sy*3.6f;
ddim.x = dim.x*13.4f;
ddim.y = dim.y*1.9f;
pe = pw->CreateEdit(pos, ddim, 0, EVENT_INTERFACE_MOD_SUMMARY);
pe->SetState(STATE_SHADOW);
pe->SetMaxChar(500);
pe->SetEditCap(false); // just to see
pe->SetHighlightCap(true);
// Apply button
pos.x = ox+sx*13.75f;
pos.y = oy+sy*2;
ddim.x = dim.x*2.0f;
ddim.y = dim.y*1;
pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_MODS_APPLY);
pb->SetState(STATE_SHADOW);
// Display the enable/disable button
pos.x -= dim.x*2.3f;
ddim.x = dim.x*2.0f;
pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE);
pb->SetState(STATE_SHADOW);
// Display the move up button
pos.x -= dim.x*0.8f;
pos.y = oy+sy*2.48;
ddim.x = dim.x*0.5;
ddim.y = dim.y*0.5;
pb = pw->CreateButton(pos, ddim, 49, EVENT_INTERFACE_MOD_MOVE_UP);
pb->SetState(STATE_SHADOW);
// Display the move down button
pos.y = oy+sy*2;
pb = pw->CreateButton(pos, ddim, 50, EVENT_INTERFACE_MOD_MOVE_DOWN);
pb->SetState(STATE_SHADOW);
// Display the refresh button
pos.x -= dim.x*1.3f;
pos.y = oy+sy*2;
ddim.x = dim.x*1;
ddim.y = dim.y*1;
pb = pw->CreateButton(pos, ddim, 87, EVENT_INTERFACE_MODS_REFRESH);
pb->SetState(STATE_SHADOW);
// Display the open website button
pos.x -= dim.x*1.3f;
pb = pw->CreateButton(pos, ddim, 40, EVENT_INTERFACE_WORKSHOP);
pb->SetState(STATE_SHADOW);
// Display the open directory button
pos.x -= dim.x*1.3f;
pb = pw->CreateButton(pos, ddim, 57, EVENT_INTERFACE_MODS_DIR);
pb->SetState(STATE_SHADOW);
// Back button
pos.x = ox+sx*3;
ddim.x = dim.x*4;
pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_BACK);
pb->SetState(STATE_SHADOW);
FindMods();
UpdateAll();
// Background
SetBackground("textures/interface/interface.png");
CreateVersionDisplay();
}
bool CScreenModList::EventProcess(const Event &event)
{
CWindow* pw;
CList* pl;
const std::string workshopUrl = "https://www.moddb.com/games/colobot-gold-edition";
const std::string modDir = CResourceManager::GetSaveLocation() + "/mods";
auto systemUtils = CSystemUtils::Create(); // platform-specific utils
Mod const * mod;
pw = static_cast<CWindow*>(m_interface->SearchControl(EVENT_WINDOW5));
if (pw == nullptr) return false;
if (event.type == pw->GetEventTypeClose() ||
event.type == EVENT_INTERFACE_BACK ||
(event.type == EVENT_KEY_DOWN && event.GetData<KeyEventData>()->key == KEY(ESCAPE)))
{
if (m_modManager->Changes())
{
m_dialog->StartQuestion(RT_DIALOG_CHANGES_QUESTION, true, true, false,
[this]()
{
ApplyChanges();
CloseWindow();
},
[this]()
{
CloseWindow();
});
}
else
{
CloseWindow();
}
return false;
}
switch( event.type )
{
case EVENT_INTERFACE_MOD_LIST:
pl = static_cast<CList*>(pw->SearchControl(EVENT_INTERFACE_MOD_LIST));
if (pl == nullptr) break;
m_modSelectedIndex = pl->GetSelect();
UpdateModSummary();
UpdateModDetails();
UpdateEnableDisableButton();
UpdateUpDownButtons();
break;
case EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE:
mod = &m_modManager->GetMod(m_modSelectedIndex);
if (mod->enabled)
{
m_modManager->DisableMod(m_modSelectedIndex);
}
else
{
m_modManager->EnableMod(m_modSelectedIndex);
}
UpdateModList();
UpdateEnableDisableButton();
UpdateApplyButton();
break;
case EVENT_INTERFACE_MOD_MOVE_UP:
m_modSelectedIndex = m_modManager->MoveUp(m_modSelectedIndex);
UpdateModList();
UpdateUpDownButtons();
UpdateApplyButton();
break;
case EVENT_INTERFACE_MOD_MOVE_DOWN:
m_modSelectedIndex = m_modManager->MoveDown(m_modSelectedIndex);
UpdateModList();
UpdateUpDownButtons();
UpdateApplyButton();
break;
case EVENT_INTERFACE_MODS_REFRESH:
// Apply any changes before refresh so that the config file
// is better synchronized with the state of the game
case EVENT_INTERFACE_MODS_APPLY:
ApplyChanges();
UpdateAll();
// Start playing the main menu music again
if (!m_app->GetSound()->IsPlayingMusic())
{
m_app->GetSound()->PlayMusic("music/Intro1.ogg", false);
m_app->GetSound()->CacheMusic("music/Intro2.ogg");
}
break;
case EVENT_INTERFACE_MODS_DIR:
if (!systemUtils->OpenPath(modDir))
{
std::string title, text;
GetResource(RES_TEXT, RT_DIALOG_OPEN_PATH_FAILED_TITLE, title);
GetResource(RES_TEXT, RT_DIALOG_OPEN_PATH_FAILED_TEXT, text);
// Workaround for Windows: the label skips everything after the first \\ character
std::string modDirWithoutBackSlashes = modDir;
std::replace(modDirWithoutBackSlashes.begin(), modDirWithoutBackSlashes.end(), '\\', '/');
m_dialog->StartInformation(title, title, StrUtils::Format(text.c_str(), modDirWithoutBackSlashes.c_str()));
}
break;
case EVENT_INTERFACE_WORKSHOP:
if (!systemUtils->OpenWebsite(workshopUrl))
{
std::string title, text;
GetResource(RES_TEXT, RT_DIALOG_OPEN_WEBSITE_FAILED_TITLE, title);
GetResource(RES_TEXT, RT_DIALOG_OPEN_WEBSITE_FAILED_TEXT, text);
m_dialog->StartInformation(title, title, StrUtils::Format(text.c_str(), workshopUrl.c_str()));
}
break;
default:
return true;
}
return false;
}
void CScreenModList::FindMods()
{
m_modManager->FindMods();
if (m_modManager->CountMods() != 0)
{
m_modSelectedIndex = glm::clamp(m_modSelectedIndex, static_cast<size_t>(0), m_modManager->CountMods() - 1);
}
}
void CScreenModList::ApplyChanges()
{
m_modManager->SaveMods();
m_modManager->ReloadMods();
}
void CScreenModList::CloseWindow()
{
m_main->ChangePhase(PHASE_MAIN_MENU);
}
void CScreenModList::UpdateAll()
{
UpdateModList();
UpdateModDetails();
UpdateModSummary();
UpdateEnableDisableButton();
UpdateApplyButton();
UpdateUpDownButtons();
}
void CScreenModList::UpdateModList()
{
CWindow* pw = static_cast<CWindow*>(m_interface->SearchControl(EVENT_WINDOW5));
if (pw == nullptr) return;
CList* pl = static_cast<CList*>(pw->SearchControl(EVENT_INTERFACE_MOD_LIST));
if (pl == nullptr) return;
pl->Flush();
if (m_modManager->CountMods() == 0)
{
return;
}
const auto& mods = m_modManager->GetMods();
for (size_t i = 0; i < mods.size(); ++i)
{
const auto& mod = mods[i];
const auto& name = mod.data.displayName;
pl->SetItemName(i, name);
pl->SetCheck(i, mod.enabled);
pl->SetEnable(i, true);
}
pl->SetSelect(m_modSelectedIndex);
pl->ShowSelect(false);
}
void CScreenModList::UpdateModDetails()
{
CWindow* pw = static_cast<CWindow*>(m_interface->SearchControl(EVENT_WINDOW5));
if (pw == nullptr) return;
CEdit* pe = static_cast<CEdit*>(pw->SearchControl(EVENT_INTERFACE_MOD_DETAILS));
if (pe == nullptr) return;
if (m_modManager->CountMods() == 0)
{
pe->SetText("No information");
return;
}
std::string details{};
const auto& mod = m_modManager->GetMod(m_modSelectedIndex);
const auto data = mod.data;
details += "\\b;" + data.displayName + '\n';
std::string authorFieldName;
GetResource(RES_TEXT, RT_MOD_AUTHOR_FIELD_NAME, authorFieldName);
details += "\\s;" + authorFieldName + " ";
if (!data.author.empty())
{
details += data.author;
}
else
{
std::string unknownAuthor;
GetResource(RES_TEXT, RT_MOD_UNKNOWN_AUTHOR, unknownAuthor);
details += unknownAuthor;
}
details += '\n';
details += '\n';
if (!data.version.empty())
{
std::string versionFieldName;
GetResource(RES_TEXT, RT_MOD_VERSION_FIELD_NAME, versionFieldName);
details += "\\t;" + versionFieldName + '\n' + data.version + '\n';
}
if (!data.website.empty())
{
std::string websiteFieldName;
GetResource(RES_TEXT, RT_MOD_WEBSITE_FIELD_NAME, websiteFieldName);
details += "\\t;" + websiteFieldName + '\n' + data.website + '\n';
}
std::string changesFieldName;
GetResource(RES_TEXT, RT_MOD_CHANGES_FIELD_NAME, changesFieldName);
details += "\\t;" + changesFieldName + '\n';
if (!data.changes.empty())
{
for (const auto& change : data.changes)
{
details += change + '\n';
}
}
else
{
std::string noChanges;
GetResource(RES_TEXT, RT_MOD_NO_CHANGES, noChanges);
details += noChanges;
}
pe->SetText(details);
pe->SetFirstLine(0);
}
void CScreenModList::UpdateModSummary()
{
CWindow* pw = static_cast<CWindow*>(m_interface->SearchControl(EVENT_WINDOW5));
if (pw == nullptr) return;
CEdit* pe = static_cast<CEdit*>(pw->SearchControl(EVENT_INTERFACE_MOD_SUMMARY));
if (pe == nullptr) return;
std::string noSummary;
GetResource(RES_TEXT, RT_MOD_NO_SUMMARY, noSummary);
if (m_modManager->CountMods() == 0)
{
pe->SetText(noSummary);
return;
}
const auto& mod = m_modManager->GetMod(m_modSelectedIndex);
if (!mod.data.summary.empty())
{
pe->SetText(mod.data.summary);
}
else
{
pe->SetText(noSummary);
}
}
void CScreenModList::UpdateEnableDisableButton()
{
CWindow* pw = static_cast<CWindow*>(m_interface->SearchControl(EVENT_WINDOW5));
if (pw == nullptr) return;
CButton* pb = static_cast<CButton*>(pw->SearchControl(EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE));
if (pb == nullptr) return;
std::string buttonName{};
if (m_modManager->CountMods() == 0)
{
pb->ClearState(STATE_ENABLE);
// Set some default name
GetResource(RES_TEXT, RT_MOD_ENABLE, buttonName);
pb->SetName(buttonName);
return;
}
const auto& mod = m_modManager->GetMod(m_modSelectedIndex);
if (mod.enabled)
{
GetResource(RES_TEXT, RT_MOD_DISABLE, buttonName);
pb->SetName(buttonName);
}
else
{
GetResource(RES_TEXT, RT_MOD_ENABLE, buttonName);
pb->SetName(buttonName);
}
}
void CScreenModList::UpdateApplyButton()
{
CWindow* pw = static_cast<CWindow*>(m_interface->SearchControl(EVENT_WINDOW5));
if (pw == nullptr) return;
CButton* pb = static_cast<CButton*>(pw->SearchControl(EVENT_INTERFACE_MODS_APPLY));
if (pb == nullptr) return;
if (m_modManager->Changes())
{
pb->SetState(STATE_ENABLE);
}
else
{
pb->ClearState(STATE_ENABLE);
}
}
void CScreenModList::UpdateUpDownButtons()
{
CWindow* pw = static_cast<CWindow*>(m_interface->SearchControl(EVENT_WINDOW5));
if (pw == nullptr) return;
CButton* pb_up = static_cast<CButton*>(pw->SearchControl(EVENT_INTERFACE_MOD_MOVE_UP));
if (pb_up == nullptr) return;
CButton* pb_down = static_cast<CButton*>(pw->SearchControl(EVENT_INTERFACE_MOD_MOVE_DOWN));
if (pb_down == nullptr) return;
if (m_modManager->CountMods() == 0)
{
pb_up->ClearState(STATE_ENABLE);
pb_down->ClearState(STATE_ENABLE);
return;
}
if (m_modSelectedIndex == 0)
{
pb_up->ClearState(STATE_ENABLE);
}
else
{
pb_up->SetState(STATE_ENABLE);
}
if (m_modSelectedIndex >= m_modManager->CountMods() - 1)
{
pb_down->ClearState(STATE_ENABLE);
}
else
{
pb_down->SetState(STATE_ENABLE);
}
}
} // namespace Ui