diff options
Diffstat (limited to 'xbmc/games/dialogs')
27 files changed, 2878 insertions, 0 deletions
diff --git a/xbmc/games/dialogs/CMakeLists.txt b/xbmc/games/dialogs/CMakeLists.txt new file mode 100644 index 0000000..d6c9a16 --- /dev/null +++ b/xbmc/games/dialogs/CMakeLists.txt @@ -0,0 +1,10 @@ +set(SOURCES GUIDialogSelectGameClient.cpp + GUIDialogSelectSavestate.cpp +) + +set(HEADERS DialogGameDefines.h + GUIDialogSelectGameClient.h + GUIDialogSelectSavestate.h +) + +core_add_library(gamedialogs) diff --git a/xbmc/games/dialogs/DialogGameDefines.h b/xbmc/games/dialogs/DialogGameDefines.h new file mode 100644 index 0000000..60dc99d --- /dev/null +++ b/xbmc/games/dialogs/DialogGameDefines.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +// Name of list item property for savestate captions +constexpr auto SAVESTATE_LABEL = "savestate.label"; +constexpr auto SAVESTATE_CAPTION = "savestate.caption"; +constexpr auto SAVESTATE_GAME_CLIENT = "savestate.gameclient"; + +// Control IDs for game dialogs +constexpr unsigned int CONTROL_VIDEO_HEADING = 10810; +constexpr unsigned int CONTROL_VIDEO_THUMBS = 10811; +constexpr unsigned int CONTROL_VIDEO_DESCRIPTION = 10812; +constexpr unsigned int CONTROL_SAVES_HEADING = 10820; +constexpr unsigned int CONTROL_SAVES_DETAILED_LIST = 3; // Select dialog defaults to this control ID +constexpr unsigned int CONTROL_SAVES_DESCRIPTION = 10822; +constexpr unsigned int CONTROL_SAVES_EMULATOR_NAME = 10823; +constexpr unsigned int CONTROL_SAVES_EMULATOR_ICON = 10824; +constexpr unsigned int CONTROL_SAVES_NEW_BUTTON = 10825; +constexpr unsigned int CONTROL_SAVES_CANCEL_BUTTON = 10826; +constexpr unsigned int CONTROL_NUMBER_OF_ITEMS = 10827; diff --git a/xbmc/games/dialogs/GUIDialogSelectGameClient.cpp b/xbmc/games/dialogs/GUIDialogSelectGameClient.cpp new file mode 100644 index 0000000..e17bc27 --- /dev/null +++ b/xbmc/games/dialogs/GUIDialogSelectGameClient.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogSelectGameClient.h" + +#include "FileItem.h" +#include "dialogs/GUIDialogSelect.h" +#include "filesystem/AddonsDirectory.h" +#include "games/addons/GameClient.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/WindowIDs.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +using namespace KODI; +using namespace KODI::MESSAGING; +using namespace GAME; + +std::string CGUIDialogSelectGameClient::ShowAndGetGameClient(const std::string& gamePath, + const GameClientVector& candidates, + const GameClientVector& installable) +{ + std::string gameClient; + + LogGameClients(candidates, installable); + + std::string extension = URIUtils::GetExtension(gamePath); + + // "Select emulator for {0:s}" + CGUIDialogSelect* dialog = + GetDialog(StringUtils::Format(g_localizeStrings.Get(35258), extension)); + if (dialog != nullptr) + { + // Turn the addons into items + CFileItemList items; + CFileItemList installableItems; + for (const auto& candidate : candidates) + { + CFileItemPtr item(XFILE::CAddonsDirectory::FileItemFromAddon(candidate, candidate->ID())); + item->SetLabel2(g_localizeStrings.Get(35257)); // "Installed" + items.Add(std::move(item)); + } + for (const auto& addon : installable) + { + CFileItemPtr item(XFILE::CAddonsDirectory::FileItemFromAddon(addon, addon->ID())); + installableItems.Add(std::move(item)); + } + items.Sort(SortByLabel, SortOrderAscending); + installableItems.Sort(SortByLabel, SortOrderAscending); + + items.Append(installableItems); + + dialog->SetItems(items); + + dialog->Open(); + + // If the "Get More" button has been pressed, show a list of installable addons + if (dialog->IsConfirmed()) + { + int selectedIndex = dialog->GetSelectedItem(); + + if (0 <= selectedIndex && selectedIndex < items.Size()) + { + gameClient = items[selectedIndex]->GetPath(); + + CLog::Log(LOGDEBUG, "Select game client dialog: User selected emulator {}", gameClient); + } + else + { + CLog::Log(LOGDEBUG, "Select game client dialog: User selected invalid emulator {}", + selectedIndex); + } + } + else + { + CLog::Log(LOGDEBUG, "Select game client dialog: User cancelled game client installation"); + } + } + + return gameClient; +} + +CGUIDialogSelect* CGUIDialogSelectGameClient::GetDialog(const std::string& title) +{ + CGUIDialogSelect* dialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>( + WINDOW_DIALOG_SELECT); + if (dialog != nullptr) + { + dialog->Reset(); + dialog->SetHeading(CVariant{title}); + dialog->SetUseDetails(true); + } + + return dialog; +} + +void CGUIDialogSelectGameClient::LogGameClients(const GameClientVector& candidates, + const GameClientVector& installable) +{ + CLog::Log(LOGDEBUG, "Select game client dialog: Found {} candidates", + static_cast<unsigned int>(candidates.size())); + for (const auto& gameClient : candidates) + CLog::Log(LOGDEBUG, "Adding {} as a candidate", gameClient->ID()); + + if (!installable.empty()) + { + CLog::Log(LOGDEBUG, "Select game client dialog: Found {} installable clients", + static_cast<unsigned int>(installable.size())); + for (const auto& gameClient : installable) + CLog::Log(LOGDEBUG, "Adding {} as an installable client", gameClient->ID()); + } +} diff --git a/xbmc/games/dialogs/GUIDialogSelectGameClient.h b/xbmc/games/dialogs/GUIDialogSelectGameClient.h new file mode 100644 index 0000000..c492481 --- /dev/null +++ b/xbmc/games/dialogs/GUIDialogSelectGameClient.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "games/GameTypes.h" + +#include <string> + +class CGUIDialogSelect; + +namespace KODI +{ +namespace GAME +{ +class CGUIDialogSelectGameClient +{ +public: + /*! + * \brief Show a series of dialogs that results in a game client being + * selected + * + * \param gamePath The path of the file being played + * \param candidates A list of installed candidates that the user can + * select from + * \param installable A list of installable candidates that the user can + * select from + * + * \return The ID of the selected game client, or empty if no game client + * was selected + */ + static std::string ShowAndGetGameClient(const std::string& gamePath, + const GameClientVector& candidates, + const GameClientVector& installable); + +private: + /*! + * \brief Get an initialized select dialog + * + * \param title The title of the select dialog + * + * \return A select dialog with its properties initialized, or nullptr if + * the dialog isn't found + */ + static CGUIDialogSelect* GetDialog(const std::string& title); + + /*! + * \brief Log the candidates and installable game clients + * + * Other than logging, this has no side effects. + */ + static void LogGameClients(const GameClientVector& candidates, + const GameClientVector& installable); +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/dialogs/GUIDialogSelectSavestate.cpp b/xbmc/games/dialogs/GUIDialogSelectSavestate.cpp new file mode 100644 index 0000000..d56b1f4 --- /dev/null +++ b/xbmc/games/dialogs/GUIDialogSelectSavestate.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogSelectSavestate.h" + +#include "ServiceBroker.h" +#include "games/dialogs/osd/DialogGameSaves.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "utils/log.h" + +using namespace KODI; +using namespace GAME; + +bool CGUIDialogSelectSavestate::ShowAndGetSavestate(const std::string& gamePath, + std::string& savestatePath) +{ + savestatePath = ""; + + // Can't ask the user if there's no dialog + CDialogGameSaves* dialog = GetDialog(); + if (dialog == nullptr) + return true; + + if (!dialog->Open(gamePath)) + return true; + + if (dialog->IsConfirmed()) + { + savestatePath = dialog->GetSelectedItemPath(); + return true; + } + else if (dialog->IsNewPressed()) + { + CLog::Log(LOGDEBUG, "Select savestate dialog: New savestate selected"); + return true; + } + + // User canceled the dialog + return false; +} + +CDialogGameSaves* CGUIDialogSelectSavestate::GetDialog() +{ + CDialogGameSaves* dialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CDialogGameSaves>( + WINDOW_DIALOG_GAME_SAVES); + + if (dialog != nullptr) + dialog->Reset(); + + return dialog; +} diff --git a/xbmc/games/dialogs/GUIDialogSelectSavestate.h b/xbmc/games/dialogs/GUIDialogSelectSavestate.h new file mode 100644 index 0000000..e554142 --- /dev/null +++ b/xbmc/games/dialogs/GUIDialogSelectSavestate.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> + +namespace KODI +{ +namespace GAME +{ +class CDialogGameSaves; + +class CGUIDialogSelectSavestate +{ +public: + static bool ShowAndGetSavestate(const std::string& gamePath, std::string& savestatePath); + +private: + static CDialogGameSaves* GetDialog(); +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/dialogs/osd/CMakeLists.txt b/xbmc/games/dialogs/osd/CMakeLists.txt new file mode 100644 index 0000000..3f7f5bd --- /dev/null +++ b/xbmc/games/dialogs/osd/CMakeLists.txt @@ -0,0 +1,25 @@ +set(SOURCES DialogGameAdvancedSettings.cpp + DialogGameOSD.cpp + DialogGameOSDHelp.cpp + DialogGameSaves.cpp + DialogGameStretchMode.cpp + DialogGameVideoFilter.cpp + DialogGameVideoRotation.cpp + DialogGameVideoSelect.cpp + DialogGameVolume.cpp + DialogInGameSaves.cpp +) + +set(HEADERS DialogGameAdvancedSettings.h + DialogGameOSD.h + DialogGameOSDHelp.h + DialogGameSaves.h + DialogGameStretchMode.h + DialogGameVideoFilter.h + DialogGameVideoRotation.h + DialogGameVideoSelect.h + DialogGameVolume.h + DialogInGameSaves.h +) + +core_add_library(gameosddialogs) diff --git a/xbmc/games/dialogs/osd/DialogGameAdvancedSettings.cpp b/xbmc/games/dialogs/osd/DialogGameAdvancedSettings.cpp new file mode 100644 index 0000000..83b7a3f --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameAdvancedSettings.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DialogGameAdvancedSettings.h" + +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/addoninfo/AddonType.h" +#include "addons/gui/GUIDialogAddonSettings.h" +#include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h" +#include "cores/RetroPlayer/guibridge/GUIGameSettingsHandle.h" +#include "guilib/GUIMessage.h" +#include "guilib/WindowIDs.h" + +using namespace KODI; +using namespace GAME; + +CDialogGameAdvancedSettings::CDialogGameAdvancedSettings() + : CGUIDialog(WINDOW_DIALOG_GAME_ADVANCED_SETTINGS, "") +{ +} + +bool CDialogGameAdvancedSettings::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_INIT: + { + auto gameSettingsHandle = CServiceBroker::GetGameRenderManager().RegisterGameSettingsDialog(); + if (gameSettingsHandle) + { + ADDON::AddonPtr addon; + if (CServiceBroker::GetAddonMgr().GetAddon(gameSettingsHandle->GameClientID(), addon, + ADDON::AddonType::GAMEDLL, + ADDON::OnlyEnabled::CHOICE_YES)) + { + gameSettingsHandle.reset(); + CGUIDialogAddonSettings::ShowForAddon(addon); + } + } + + return false; + } + default: + break; + } + + return CGUIDialog::OnMessage(message); +} diff --git a/xbmc/games/dialogs/osd/DialogGameAdvancedSettings.h b/xbmc/games/dialogs/osd/DialogGameAdvancedSettings.h new file mode 100644 index 0000000..00b669b --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameAdvancedSettings.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +namespace KODI +{ +namespace GAME +{ +class CDialogGameAdvancedSettings : public CGUIDialog +{ +public: + CDialogGameAdvancedSettings(); + ~CDialogGameAdvancedSettings() override = default; + + // implementation of CGUIControl via CGUIDialog + bool OnMessage(CGUIMessage& message) override; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/dialogs/osd/DialogGameOSD.cpp b/xbmc/games/dialogs/osd/DialogGameOSD.cpp new file mode 100644 index 0000000..3a0eb52 --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameOSD.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DialogGameOSD.h" + +#include "DialogGameOSDHelp.h" +#include "ServiceBroker.h" +#include "games/GameServices.h" +#include "games/GameSettings.h" +#include "guilib/WindowIDs.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" + +using namespace KODI; +using namespace GAME; + +CDialogGameOSD::CDialogGameOSD() + : CGUIDialog(WINDOW_DIALOG_GAME_OSD, "GameOSD.xml"), m_helpDialog(new CDialogGameOSDHelp(*this)) +{ + // Initialize CGUIWindow + m_loadType = KEEP_IN_MEMORY; +} + +bool CDialogGameOSD::OnAction(const CAction& action) +{ + switch (action.GetID()) + { + case ACTION_PARENT_DIR: + case ACTION_PREVIOUS_MENU: + case ACTION_NAV_BACK: + case ACTION_SHOW_OSD: + case ACTION_PLAYER_PLAY: + { + // Disable OSD help if visible + if (m_helpDialog->IsVisible() && CServiceBroker::IsServiceManagerUp()) + { + GAME::CGameSettings& gameSettings = CServiceBroker::GetGameServices().GameSettings(); + if (gameSettings.ShowOSDHelp()) + { + gameSettings.SetShowOSDHelp(false); + return true; + } + } + break; + } + default: + break; + } + + return CGUIDialog::OnAction(action); +} + +void CDialogGameOSD::OnInitWindow() +{ + // Init parent class + CGUIDialog::OnInitWindow(); + + // Init help dialog + m_helpDialog->OnInitWindow(); +} + +void CDialogGameOSD::OnDeinitWindow(int nextWindowID) +{ + CGUIDialog::OnDeinitWindow(nextWindowID); + + if (CServiceBroker::IsServiceManagerUp()) + { + GAME::CGameSettings& gameSettings = CServiceBroker::GetGameServices().GameSettings(); + gameSettings.SetShowOSDHelp(false); + } +} + +bool CDialogGameOSD::PlayInBackground(int dialogId) +{ + return dialogId == WINDOW_DIALOG_GAME_VOLUME; +} diff --git a/xbmc/games/dialogs/osd/DialogGameOSD.h b/xbmc/games/dialogs/osd/DialogGameOSD.h new file mode 100644 index 0000000..3a8127b --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameOSD.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +#include <memory> + +namespace KODI +{ +namespace GAME +{ +class CDialogGameOSDHelp; + +class CDialogGameOSD : public CGUIDialog +{ +public: + CDialogGameOSD(); + + ~CDialogGameOSD() override = default; + + // Implementation of CGUIControl via CGUIDialog + bool OnAction(const CAction& action) override; + + // Implementation of CGUIWindow via CGUIDialog + void OnDeinitWindow(int nextWindowID) override; + + /*! + * \brief Decide if the game should play behind the given dialog + * + * If true, the game should be played at regular speed. + * + * \param dialog The current dialog + * + * \return True if the game should be played at regular speed behind the + * dialog, false otherwise + */ + static bool PlayInBackground(int dialogId); + +protected: + // Implementation of CGUIWindow via CGUIDialog + void OnInitWindow() override; + +private: + std::unique_ptr<CDialogGameOSDHelp> m_helpDialog; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/dialogs/osd/DialogGameOSDHelp.cpp b/xbmc/games/dialogs/osd/DialogGameOSDHelp.cpp new file mode 100644 index 0000000..3152172 --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameOSDHelp.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DialogGameOSDHelp.h" + +#include "DialogGameOSD.h" +#include "ServiceBroker.h" +#include "games/GameServices.h" +#include "games/controllers/guicontrols/GUIGameController.h" +#include "guilib/GUIMessage.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/WindowIDs.h" +#include "utils/StringUtils.h" + +using namespace KODI; +using namespace GAME; + +const int CDialogGameOSDHelp::CONTROL_ID_HELP_TEXT = 1101; +const int CDialogGameOSDHelp::CONTROL_ID_GAME_CONTROLLER = 1102; + +CDialogGameOSDHelp::CDialogGameOSDHelp(CDialogGameOSD& dialog) : m_dialog(dialog) +{ +} + +void CDialogGameOSDHelp::OnInitWindow() +{ + // Set help text + //! @todo Define Select + X combo elsewhere + // "Press {0:s} to open the menu." + std::string helpText = StringUtils::Format(g_localizeStrings.Get(35235), "Select + X"); + + CGUIMessage msg(GUI_MSG_LABEL_SET, WINDOW_DIALOG_GAME_OSD, CONTROL_ID_HELP_TEXT); + msg.SetLabel(helpText); + m_dialog.OnMessage(msg); + + // Set controller + if (CServiceBroker::IsServiceManagerUp()) + { + CGameServices& gameServices = CServiceBroker::GetGameServices(); + + //! @todo Define SNES controller elsewhere + ControllerPtr controller = gameServices.GetController("game.controller.snes"); + if (controller) + { + //! @todo Activate controller for all game controller controls + CGUIGameController* guiController = + dynamic_cast<CGUIGameController*>(m_dialog.GetControl(CONTROL_ID_GAME_CONTROLLER)); + if (guiController != nullptr) + guiController->ActivateController(controller); + } + } +} + +bool CDialogGameOSDHelp::IsVisible() +{ + return IsVisible(CONTROL_ID_HELP_TEXT) || IsVisible(CONTROL_ID_GAME_CONTROLLER); +} + +bool CDialogGameOSDHelp::IsVisible(int windowId) +{ + CGUIControl* control = m_dialog.GetControl(windowId); + if (control != nullptr) + return control->IsVisible(); + + return false; +} diff --git a/xbmc/games/dialogs/osd/DialogGameOSDHelp.h b/xbmc/games/dialogs/osd/DialogGameOSDHelp.h new file mode 100644 index 0000000..6153075 --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameOSDHelp.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +namespace KODI +{ +namespace GAME +{ +class CDialogGameOSD; + +class CDialogGameOSDHelp +{ +public: + CDialogGameOSDHelp(CDialogGameOSD& dialog); + + // Initialize help controls + void OnInitWindow(); + + // Check if any help controls are visible + bool IsVisible(); + +private: + // Utility functions + bool IsVisible(int windowId); + + // Construction parameters + CDialogGameOSD& m_dialog; + + // Help control IDs + static const int CONTROL_ID_HELP_TEXT; + static const int CONTROL_ID_GAME_CONTROLLER; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/dialogs/osd/DialogGameSaves.cpp b/xbmc/games/dialogs/osd/DialogGameSaves.cpp new file mode 100644 index 0000000..90e7785 --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameSaves.cpp @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2020-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DialogGameSaves.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "addons/Addon.h" +#include "addons/AddonManager.h" +#include "cores/RetroPlayer/savestates/ISavestate.h" +#include "cores/RetroPlayer/savestates/SavestateDatabase.h" +#include "dialogs/GUIDialogContextMenu.h" +#include "dialogs/GUIDialogOK.h" +#include "dialogs/GUIDialogYesNo.h" +#include "games/addons/GameClient.h" +#include "games/dialogs/DialogGameDefines.h" +#include "guilib/GUIBaseContainer.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/WindowIDs.h" +#include "input/Key.h" +#include "utils/FileUtils.h" +#include "utils/Variant.h" +#include "view/GUIViewControl.h" +#include "view/ViewState.h" + +using namespace KODI; +using namespace GAME; + +CDialogGameSaves::CDialogGameSaves() + : CGUIDialog(WINDOW_DIALOG_GAME_SAVES, "DialogSelect.xml"), + m_viewControl(std::make_unique<CGUIViewControl>()), + m_vecList(std::make_unique<CFileItemList>()) +{ +} + +bool CDialogGameSaves::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_CLICKED: + { + const int actionId = message.GetParam1(); + + switch (actionId) + { + case ACTION_SELECT_ITEM: + case ACTION_MOUSE_LEFT_CLICK: + { + int selectedId = m_viewControl->GetSelectedItem(); + if (0 <= selectedId && selectedId < m_vecList->Size()) + { + CFileItemPtr item = m_vecList->Get(selectedId); + if (item) + { + for (int i = 0; i < m_vecList->Size(); i++) + m_vecList->Get(i)->Select(false); + + item->Select(true); + + OnSelect(*item); + + return true; + } + } + break; + } + case ACTION_CONTEXT_MENU: + case ACTION_MOUSE_RIGHT_CLICK: + { + int selectedItem = m_viewControl->GetSelectedItem(); + if (selectedItem >= 0 && selectedItem < m_vecList->Size()) + { + CFileItemPtr item = m_vecList->Get(selectedItem); + if (item) + { + OnContextMenu(*item); + return true; + } + } + break; + } + case ACTION_RENAME_ITEM: + { + const int controlId = message.GetSenderId(); + if (m_viewControl->HasControl(controlId)) + { + int selectedItem = m_viewControl->GetSelectedItem(); + if (selectedItem >= 0 && selectedItem < m_vecList->Size()) + { + CFileItemPtr item = m_vecList->Get(selectedItem); + if (item) + { + OnRename(*item); + return true; + } + } + } + break; + } + case ACTION_DELETE_ITEM: + { + const int controlId = message.GetSenderId(); + if (m_viewControl->HasControl(controlId)) + { + int selectedItem = m_viewControl->GetSelectedItem(); + if (selectedItem >= 0 && selectedItem < m_vecList->Size()) + { + CFileItemPtr item = m_vecList->Get(selectedItem); + if (item) + { + OnDelete(*item); + return true; + } + } + } + break; + } + default: + break; + } + + const int controlId = message.GetSenderId(); + switch (controlId) + { + case CONTROL_SAVES_NEW_BUTTON: + { + m_bNewPressed = true; + Close(); + break; + } + case CONTROL_SAVES_CANCEL_BUTTON: + { + m_selectedItem.reset(); + m_vecList->Clear(); + m_bConfirmed = false; + Close(); + break; + } + default: + break; + } + + break; + } + + case GUI_MSG_SETFOCUS: + { + const int controlId = message.GetControlId(); + if (m_viewControl->HasControl(controlId)) + { + if (m_vecList->IsEmpty()) + { + SET_CONTROL_FOCUS(CONTROL_SAVES_NEW_BUTTON, 0); + return true; + } + + if (m_viewControl->GetCurrentControl() != controlId) + { + m_viewControl->SetFocused(); + return true; + } + } + break; + } + + default: + break; + } + + return CGUIDialog::OnMessage(message); +} + +void CDialogGameSaves::FrameMove() +{ + CGUIControl* itemContainer = GetControl(CONTROL_SAVES_DETAILED_LIST); + if (itemContainer != nullptr) + { + if (itemContainer->HasFocus()) + { + int selectedItem = m_viewControl->GetSelectedItem(); + if (selectedItem >= 0 && selectedItem < m_vecList->Size()) + { + CFileItemPtr item = m_vecList->Get(selectedItem); + if (item) + OnFocus(*item); + } + } + else + { + OnFocusLost(); + } + } + + CGUIDialog::FrameMove(); +} + +void CDialogGameSaves::OnInitWindow() +{ + m_viewControl->SetItems(*m_vecList); + m_viewControl->SetCurrentView(CONTROL_SAVES_DETAILED_LIST); + + CGUIDialog::OnInitWindow(); + + // Select the first item + m_viewControl->SetSelectedItem(0); + + // There's a race condition where the item's focus sends the update message + // before the window is fully initialized, so explicitly set the info now. + if (!m_vecList->IsEmpty()) + { + CFileItemPtr item = m_vecList->Get(0); + if (item) + { + const std::string gameClientId = item->GetProperty(SAVESTATE_GAME_CLIENT).asString(); + if (!gameClientId.empty()) + { + std::string emulatorName; + std::string emulatorIcon; + + using namespace ADDON; + + AddonPtr addon; + CAddonMgr& addonManager = CServiceBroker::GetAddonMgr(); + if (addonManager.GetAddon(m_currentGameClient, addon, OnlyEnabled::CHOICE_NO)) + { + std::shared_ptr<CGameClient> gameClient = std::dynamic_pointer_cast<CGameClient>(addon); + if (gameClient) + { + m_currentGameClient = gameClient->ID(); + + emulatorName = gameClient->GetEmulatorName(); + emulatorIcon = gameClient->Icon(); + } + } + + if (!emulatorName.empty()) + { + CGUIMessage message(GUI_MSG_LABEL_SET, GetID(), CONTROL_SAVES_EMULATOR_NAME); + message.SetLabel(emulatorName); + OnMessage(message); + } + if (!emulatorIcon.empty()) + { + CGUIMessage message(GUI_MSG_SET_FILENAME, GetID(), CONTROL_SAVES_EMULATOR_ICON); + message.SetLabel(emulatorIcon); + OnMessage(message); + } + } + + const std::string caption = item->GetProperty(SAVESTATE_CAPTION).asString(); + if (!caption.empty()) + { + m_currentCaption = caption; + + CGUIMessage message(GUI_MSG_LABEL_SET, GetID(), CONTROL_SAVES_DESCRIPTION); + message.SetLabel(m_currentCaption); + OnMessage(message); + } + } + } +} + +void CDialogGameSaves::OnDeinitWindow(int nextWindowID) +{ + m_viewControl->Clear(); + + CGUIDialog::OnDeinitWindow(nextWindowID); + + // Get selected item + for (int i = 0; i < m_vecList->Size(); ++i) + { + CFileItemPtr item = m_vecList->Get(i); + if (item->IsSelected()) + { + m_selectedItem = item; + break; + } + } + + m_vecList->Clear(); +} + +void CDialogGameSaves::OnWindowLoaded() +{ + CGUIDialog::OnWindowLoaded(); + + m_viewControl->Reset(); + m_viewControl->SetParentWindow(GetID()); + m_viewControl->AddView(GetControl(CONTROL_SAVES_DETAILED_LIST)); +} + +void CDialogGameSaves::OnWindowUnload() +{ + CGUIDialog::OnWindowUnload(); + m_viewControl->Reset(); +} + +void CDialogGameSaves::Reset() +{ + m_bConfirmed = false; + m_bNewPressed = false; + + m_vecList->Clear(); + m_selectedItem.reset(); +} + +bool CDialogGameSaves::Open(const std::string& gamePath) +{ + CFileItemList items; + + RETRO::CSavestateDatabase db; + if (!db.GetSavestatesNav(items, gamePath)) + return false; + + if (items.IsEmpty()) + return false; + + items.Sort(SortByDate, SortOrderDescending); + + SetItems(items); + + CGUIDialog::Open(); + + return true; +} + +std::string CDialogGameSaves::GetSelectedItemPath() +{ + if (m_selectedItem) + return m_selectedItem->GetPath(); + + return ""; +} + +void CDialogGameSaves::SetItems(const CFileItemList& itemList) +{ + m_vecList->Clear(); + + // Need to make internal copy of list to be sure dialog is owner of it + m_vecList->Copy(itemList); + + m_viewControl->SetItems(*m_vecList); +} + +void CDialogGameSaves::OnSelect(const CFileItem& item) +{ + m_bConfirmed = true; + Close(); +} + +void CDialogGameSaves::OnFocus(const CFileItem& item) +{ + const std::string caption = item.GetProperty(SAVESTATE_CAPTION).asString(); + const std::string gameClientId = item.GetProperty(SAVESTATE_GAME_CLIENT).asString(); + + HandleCaption(caption); + HandleGameClient(gameClientId); +} + +void CDialogGameSaves::OnFocusLost() +{ + HandleCaption(""); + HandleGameClient(""); +} + +void CDialogGameSaves::OnContextMenu(CFileItem& item) +{ + CContextButtons buttons; + + buttons.Add(0, 118); // "Rename" + buttons.Add(1, 117); // "Delete" + + const int index = CGUIDialogContextMenu::Show(buttons); + + if (index == 0) + OnRename(item); + else if (index == 1) + OnDelete(item); +} + +void CDialogGameSaves::OnRename(CFileItem& item) +{ + const std::string& savestatePath = item.GetPath(); + + // Get savestate properties + RETRO::CSavestateDatabase db; + std::unique_ptr<RETRO::ISavestate> savestate = RETRO::CSavestateDatabase::AllocateSavestate(); + db.GetSavestate(savestatePath, *savestate); + + std::string label(savestate->Label()); + + // "Enter new filename" + if (CGUIKeyboardFactory::ShowAndGetInput(label, CVariant{g_localizeStrings.Get(16013)}, true) && + label != savestate->Label()) + { + std::unique_ptr<RETRO::ISavestate> newSavestate = db.RenameSavestate(savestatePath, label); + if (newSavestate) + { + RETRO::CSavestateDatabase::GetSavestateItem(*newSavestate, savestatePath, item); + + // Refresh thumbnails + m_viewControl->SetItems(*m_vecList); + } + else + { + // "Error" + // "An unknown error has occurred." + CGUIDialogOK::ShowAndGetInput(257, 24071); + } + } +} + +void CDialogGameSaves::OnDelete(CFileItem& item) +{ + // "Confirm delete" + // "Would you like to delete the selected file(s)?[CR]Warning - this action can't be undone!" + if (CGUIDialogYesNo::ShowAndGetInput(CVariant{122}, CVariant{125})) + { + const std::string& savestatePath = item.GetPath(); + + RETRO::CSavestateDatabase db; + if (db.DeleteSavestate(savestatePath)) + { + m_vecList->Remove(&item); + + // Refresh thumbnails + m_viewControl->SetItems(*m_vecList); + } + else + { + // "Error" + // "An unknown error has occurred." + CGUIDialogOK::ShowAndGetInput(257, 24071); + } + } +} + +void CDialogGameSaves::HandleCaption(const std::string& caption) +{ + if (caption != m_currentCaption) + { + m_currentCaption = caption; + + // Update the GUI label + CGUIMessage msg(GUI_MSG_LABEL_SET, GetID(), CONTROL_SAVES_DESCRIPTION); + msg.SetLabel(m_currentCaption); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, GetID()); + } +} + +void CDialogGameSaves::HandleGameClient(const std::string& gameClientId) +{ + if (gameClientId == m_currentGameClient) + return; + + m_currentGameClient = gameClientId; + if (m_currentGameClient.empty()) + return; + + // Get game client properties + std::shared_ptr<CGameClient> gameClient; + std::string emulatorName; + std::string iconPath; + + using namespace ADDON; + + AddonPtr addon; + CAddonMgr& addonManager = CServiceBroker::GetAddonMgr(); + if (addonManager.GetAddon(m_currentGameClient, addon, OnlyEnabled::CHOICE_NO)) + gameClient = std::dynamic_pointer_cast<CGameClient>(addon); + + if (gameClient) + { + emulatorName = gameClient->GetEmulatorName(); + iconPath = gameClient->Icon(); + } + + // Update the GUI elements + if (!emulatorName.empty()) + { + CGUIMessage message(GUI_MSG_LABEL_SET, GetID(), CONTROL_SAVES_EMULATOR_NAME); + message.SetLabel(emulatorName); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message, GetID()); + } + if (!iconPath.empty()) + { + CGUIMessage message(GUI_MSG_SET_FILENAME, GetID(), CONTROL_SAVES_EMULATOR_ICON); + message.SetLabel(iconPath); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message, GetID()); + } +} diff --git a/xbmc/games/dialogs/osd/DialogGameSaves.h b/xbmc/games/dialogs/osd/DialogGameSaves.h new file mode 100644 index 0000000..3605ee6 --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameSaves.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2020-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +#include <memory> +#include <string> + +class CFileItem; +class CFileItemList; +class CGUIMessage; +class CGUIViewControl; + +namespace KODI +{ +namespace GAME +{ +class CDialogGameSaves : public CGUIDialog +{ +public: + CDialogGameSaves(); + ~CDialogGameSaves() override = default; + + // implementation of CGUIControl via CGUIDialog + bool OnMessage(CGUIMessage& message) override; + + // implementation of CGUIWindow via CGUIDialog + void FrameMove() override; + + // Player interface + void Reset(); + bool Open(const std::string& gamePath); + bool IsConfirmed() const { return m_bConfirmed; } + bool IsNewPressed() const { return m_bNewPressed; } + std::string GetSelectedItemPath(); + +protected: + // implementation of CGUIWIndow via CGUIDialog + void OnInitWindow() override; + void OnDeinitWindow(int nextWindowID) override; + void OnWindowLoaded() override; + void OnWindowUnload() override; + +private: + using CGUIControl::OnFocus; + + /*! + * \breif Called when opening to set the item list + */ + void SetItems(const CFileItemList& itemList); + + /*! + * \brief Called when an item has been selected + */ + void OnSelect(const CFileItem& item); + + /*! + * \brief Called every frame with the item being focused + */ + void OnFocus(const CFileItem& item); + + /*! + * \brief Called every frame if no item is focused + */ + void OnFocusLost(); + + /*! + * \brief Called when a context menu is opened for an item + */ + void OnContextMenu(CFileItem& item); + + /*! + * \brief Called when "Rename" is selected from the context menu + */ + void OnRename(CFileItem& item); + + /*! + * \brief Called when "Delete" is selected from the context menu + */ + void OnDelete(CFileItem& item); + + /*! + * \brief Called every frame with the caption to set + */ + void HandleCaption(const std::string& caption); + + /*! + * \brief Called every frame with the game client to set + */ + void HandleGameClient(const std::string& gameClientId); + + // Dialog parameters + std::unique_ptr<CGUIViewControl> m_viewControl; + std::unique_ptr<CFileItemList> m_vecList; + std::shared_ptr<CFileItem> m_selectedItem; + + // Player parameters + bool m_bConfirmed{false}; + bool m_bNewPressed{false}; + + // State parameters + std::string m_currentCaption; + std::string m_currentGameClient; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/dialogs/osd/DialogGameStretchMode.cpp b/xbmc/games/dialogs/osd/DialogGameStretchMode.cpp new file mode 100644 index 0000000..98c1e07 --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameStretchMode.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DialogGameStretchMode.h" + +#include "FileItem.h" +#include "cores/RetroPlayer/RetroPlayerUtils.h" +#include "cores/RetroPlayer/guibridge/GUIGameVideoHandle.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/WindowIDs.h" +#include "settings/GameSettings.h" +#include "settings/MediaSettings.h" +#include "utils/Variant.h" + +using namespace KODI; +using namespace GAME; + +const std::vector<CDialogGameStretchMode::StretchModeProperties> + CDialogGameStretchMode::m_allStretchModes = { + {630, RETRO::STRETCHMODE::Normal}, + // { 631, RETRO::STRETCHMODE::Zoom }, //! @todo RetroArch allows trimming some outer + // pixels + {632, RETRO::STRETCHMODE::Stretch4x3}, + {35232, RETRO::STRETCHMODE::Fullscreen}, + {635, RETRO::STRETCHMODE::Original}, +}; + +CDialogGameStretchMode::CDialogGameStretchMode() + : CDialogGameVideoSelect(WINDOW_DIALOG_GAME_STRETCH_MODE) +{ +} + +std::string CDialogGameStretchMode::GetHeading() +{ + return g_localizeStrings.Get(35233); // "Stretch mode" +} + +void CDialogGameStretchMode::PreInit() +{ + m_stretchModes.clear(); + + for (const auto& stretchMode : m_allStretchModes) + { + bool bSupported = false; + + switch (stretchMode.stretchMode) + { + case RETRO::STRETCHMODE::Normal: + case RETRO::STRETCHMODE::Original: + bSupported = true; + break; + + case RETRO::STRETCHMODE::Stretch4x3: + case RETRO::STRETCHMODE::Fullscreen: + if (m_gameVideoHandle) + { + bSupported = m_gameVideoHandle->SupportsRenderFeature(RETRO::RENDERFEATURE::STRETCH) || + m_gameVideoHandle->SupportsRenderFeature(RETRO::RENDERFEATURE::PIXEL_RATIO); + } + break; + + default: + break; + } + + if (bSupported) + m_stretchModes.emplace_back(stretchMode); + } +} + +void CDialogGameStretchMode::GetItems(CFileItemList& items) +{ + for (const auto& stretchMode : m_stretchModes) + { + CFileItemPtr item = std::make_shared<CFileItem>(g_localizeStrings.Get(stretchMode.stringIndex)); + + const std::string stretchModeId = + RETRO::CRetroPlayerUtils::StretchModeToIdentifier(stretchMode.stretchMode); + if (!stretchModeId.empty()) + item->SetProperty("game.stretchmode", CVariant{stretchModeId}); + items.Add(std::move(item)); + } +} + +void CDialogGameStretchMode::OnItemFocus(unsigned int index) +{ + if (index < m_stretchModes.size()) + { + const RETRO::STRETCHMODE stretchMode = m_stretchModes[index].stretchMode; + + CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings(); + if (gameSettings.StretchMode() != stretchMode) + { + gameSettings.SetStretchMode(stretchMode); + gameSettings.NotifyObservers(ObservableMessageSettingsChanged); + } + } +} + +unsigned int CDialogGameStretchMode::GetFocusedItem() const +{ + CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings(); + + for (unsigned int i = 0; i < m_stretchModes.size(); i++) + { + const RETRO::STRETCHMODE stretchMode = m_stretchModes[i].stretchMode; + if (stretchMode == gameSettings.StretchMode()) + return i; + } + + return 0; +} + +void CDialogGameStretchMode::PostExit() +{ + m_stretchModes.clear(); +} + +bool CDialogGameStretchMode::OnClickAction() +{ + Close(); + return true; +} diff --git a/xbmc/games/dialogs/osd/DialogGameStretchMode.h b/xbmc/games/dialogs/osd/DialogGameStretchMode.h new file mode 100644 index 0000000..1e05061 --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameStretchMode.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DialogGameVideoSelect.h" +#include "cores/GameSettings.h" + +#include <vector> + +namespace KODI +{ +namespace GAME +{ +class CDialogGameStretchMode : public CDialogGameVideoSelect +{ +public: + CDialogGameStretchMode(); + ~CDialogGameStretchMode() override = default; + +protected: + // implementation of CDialogGameVideoSelect + std::string GetHeading() override; + void PreInit() override; + void GetItems(CFileItemList& items) override; + void OnItemFocus(unsigned int index) override; + unsigned int GetFocusedItem() const override; + void PostExit() override; + bool OnClickAction() override; + +private: + struct StretchModeProperties + { + int stringIndex; + RETRO::STRETCHMODE stretchMode; + }; + + std::vector<StretchModeProperties> m_stretchModes; + + /*! + * \brief The list of all the stretch modes along with their properties + */ + static const std::vector<StretchModeProperties> m_allStretchModes; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/dialogs/osd/DialogGameVideoFilter.cpp b/xbmc/games/dialogs/osd/DialogGameVideoFilter.cpp new file mode 100644 index 0000000..a5bba0b --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameVideoFilter.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DialogGameVideoFilter.h" + +#include "cores/RetroPlayer/guibridge/GUIGameVideoHandle.h" +#include "cores/RetroPlayer/rendering/RenderVideoSettings.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/WindowIDs.h" +#include "settings/GameSettings.h" +#include "settings/MediaSettings.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +using namespace KODI; +using namespace GAME; + +namespace +{ +struct ScalingMethodProperties +{ + int nameIndex; + int categoryIndex; + int descriptionIndex; + RETRO::SCALINGMETHOD scalingMethod; +}; + +const std::vector<ScalingMethodProperties> scalingMethods = { + {16301, 16296, 16298, RETRO::SCALINGMETHOD::NEAREST}, + {16302, 16297, 16299, RETRO::SCALINGMETHOD::LINEAR}, +}; +} // namespace + +CDialogGameVideoFilter::CDialogGameVideoFilter() + : CDialogGameVideoSelect(WINDOW_DIALOG_GAME_VIDEO_FILTER) +{ +} + +std::string CDialogGameVideoFilter::GetHeading() +{ + return g_localizeStrings.Get(35225); // "Video filter" +} + +void CDialogGameVideoFilter::PreInit() +{ + m_items.Clear(); + + InitVideoFilters(); + + if (m_items.Size() == 0) + { + CFileItemPtr item = std::make_shared<CFileItem>(g_localizeStrings.Get(231)); // "None" + m_items.Add(std::move(item)); + } + + m_bHasDescription = false; +} + +void CDialogGameVideoFilter::InitVideoFilters() +{ + if (m_gameVideoHandle) + { + for (const auto& scalingMethodProps : scalingMethods) + { + if (m_gameVideoHandle->SupportsScalingMethod(scalingMethodProps.scalingMethod)) + { + RETRO::CRenderVideoSettings videoSettings; + videoSettings.SetScalingMethod(scalingMethodProps.scalingMethod); + + CFileItemPtr item = + std::make_shared<CFileItem>(g_localizeStrings.Get(scalingMethodProps.nameIndex)); + item->SetLabel2(g_localizeStrings.Get(scalingMethodProps.categoryIndex)); + item->SetProperty("game.videofilter", CVariant{videoSettings.GetVideoFilter()}); + item->SetProperty("game.videofilterdescription", + CVariant{g_localizeStrings.Get(scalingMethodProps.descriptionIndex)}); + m_items.Add(std::move(item)); + } + } + } +} + +void CDialogGameVideoFilter::GetItems(CFileItemList& items) +{ + for (const auto& item : m_items) + items.Add(item); +} + +void CDialogGameVideoFilter::OnItemFocus(unsigned int index) +{ + if (static_cast<int>(index) < m_items.Size()) + { + CFileItemPtr item = m_items[index]; + + std::string videoFilter; + std::string description; + GetProperties(*item, videoFilter, description); + + CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings(); + + if (gameSettings.VideoFilter() != videoFilter) + { + gameSettings.SetVideoFilter(videoFilter); + gameSettings.NotifyObservers(ObservableMessageSettingsChanged); + + OnDescriptionChange(description); + m_bHasDescription = true; + } + else if (!m_bHasDescription) + { + OnDescriptionChange(description); + m_bHasDescription = true; + } + } +} + +unsigned int CDialogGameVideoFilter::GetFocusedItem() const +{ + CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings(); + + for (int i = 0; i < m_items.Size(); i++) + { + std::string videoFilter; + std::string description; + GetProperties(*m_items[i], videoFilter, description); + + if (videoFilter == gameSettings.VideoFilter()) + { + return i; + } + } + + return 0; +} + +void CDialogGameVideoFilter::PostExit() +{ + m_items.Clear(); +} + +bool CDialogGameVideoFilter::OnClickAction() +{ + Close(); + return true; +} + +void CDialogGameVideoFilter::GetProperties(const CFileItem& item, + std::string& videoFilter, + std::string& description) +{ + videoFilter = item.GetProperty("game.videofilter").asString(); + description = item.GetProperty("game.videofilterdescription").asString(); +} diff --git a/xbmc/games/dialogs/osd/DialogGameVideoFilter.h b/xbmc/games/dialogs/osd/DialogGameVideoFilter.h new file mode 100644 index 0000000..7ec8c76 --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameVideoFilter.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DialogGameVideoSelect.h" +#include "FileItem.h" + +namespace KODI +{ +namespace GAME +{ +class CDialogGameVideoFilter : public CDialogGameVideoSelect +{ +public: + CDialogGameVideoFilter(); + ~CDialogGameVideoFilter() override = default; + +protected: + // implementation of CDialogGameVideoSelect + std::string GetHeading() override; + void PreInit() override; + void GetItems(CFileItemList& items) override; + void OnItemFocus(unsigned int index) override; + unsigned int GetFocusedItem() const override; + void PostExit() override; + bool OnClickAction() override; + +private: + void InitVideoFilters(); + + static void GetProperties(const CFileItem& item, + std::string& videoFilter, + std::string& description); + + CFileItemList m_items; + + //! \brief Set to true when a description has first been set + bool m_bHasDescription = false; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/dialogs/osd/DialogGameVideoRotation.cpp b/xbmc/games/dialogs/osd/DialogGameVideoRotation.cpp new file mode 100644 index 0000000..7037a6b --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameVideoRotation.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DialogGameVideoRotation.h" + +#include "FileItem.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/WindowIDs.h" +#include "settings/GameSettings.h" +#include "settings/MediaSettings.h" +#include "utils/Variant.h" + +using namespace KODI; +using namespace GAME; + +CDialogGameVideoRotation::CDialogGameVideoRotation() + : CDialogGameVideoSelect(WINDOW_DIALOG_GAME_VIDEO_ROTATION) +{ +} + +std::string CDialogGameVideoRotation::GetHeading() +{ + return g_localizeStrings.Get(35227); // "Rotation" +} + +void CDialogGameVideoRotation::PreInit() +{ + m_rotations.clear(); + + // Present the user with clockwise rotation + m_rotations.push_back(0); + m_rotations.push_back(270); + m_rotations.push_back(180); + m_rotations.push_back(90); +} + +void CDialogGameVideoRotation::GetItems(CFileItemList& items) +{ + for (unsigned int rotation : m_rotations) + { + CFileItemPtr item = std::make_shared<CFileItem>(GetRotationLabel(rotation)); + item->SetProperty("game.videorotation", CVariant{rotation}); + items.Add(std::move(item)); + } +} + +void CDialogGameVideoRotation::OnItemFocus(unsigned int index) +{ + if (index < m_rotations.size()) + { + const unsigned int rotationDegCCW = m_rotations[index]; + + CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings(); + if (gameSettings.RotationDegCCW() != rotationDegCCW) + { + gameSettings.SetRotationDegCCW(rotationDegCCW); + gameSettings.NotifyObservers(ObservableMessageSettingsChanged); + } + } +} + +unsigned int CDialogGameVideoRotation::GetFocusedItem() const +{ + CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings(); + + for (unsigned int i = 0; i < m_rotations.size(); i++) + { + const unsigned int rotationDegCCW = m_rotations[i]; + if (rotationDegCCW == gameSettings.RotationDegCCW()) + return i; + } + + return 0; +} + +void CDialogGameVideoRotation::PostExit() +{ + m_rotations.clear(); +} + +bool CDialogGameVideoRotation::OnClickAction() +{ + Close(); + return true; +} + +std::string CDialogGameVideoRotation::GetRotationLabel(unsigned int rotationDegCCW) +{ + switch (rotationDegCCW) + { + case 0: + return g_localizeStrings.Get(35228); // 0 + case 90: + return g_localizeStrings.Get(35231); // 270 + case 180: + return g_localizeStrings.Get(35230); // 180 + case 270: + return g_localizeStrings.Get(35229); // 90 + default: + break; + } + + return ""; +} diff --git a/xbmc/games/dialogs/osd/DialogGameVideoRotation.h b/xbmc/games/dialogs/osd/DialogGameVideoRotation.h new file mode 100644 index 0000000..ef454db --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameVideoRotation.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DialogGameVideoSelect.h" + +#include <string> +#include <vector> + +namespace KODI +{ +namespace GAME +{ +class CDialogGameVideoRotation : public CDialogGameVideoSelect +{ +public: + CDialogGameVideoRotation(); + ~CDialogGameVideoRotation() override = default; + +protected: + // implementation of CDialogGameVideoSelect + std::string GetHeading() override; + void PreInit() override; + void GetItems(CFileItemList& items) override; + void OnItemFocus(unsigned int index) override; + unsigned int GetFocusedItem() const override; + void PostExit() override; + bool OnClickAction() override; + +private: + // Helper functions + static std::string GetRotationLabel(unsigned int rotationDegCCW); + + // Dialog parameters + std::vector<unsigned int> m_rotations; // Degrees counter-clockwise +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/dialogs/osd/DialogGameVideoSelect.cpp b/xbmc/games/dialogs/osd/DialogGameVideoSelect.cpp new file mode 100644 index 0000000..a69b26f --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameVideoSelect.cpp @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DialogGameVideoSelect.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h" +#include "cores/RetroPlayer/guibridge/GUIGameVideoHandle.h" +#include "games/dialogs/DialogGameDefines.h" +#include "guilib/GUIBaseContainer.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindowManager.h" +#include "input/actions/ActionIDs.h" +#include "settings/GameSettings.h" +#include "settings/MediaSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "view/GUIViewControl.h" +#include "view/ViewState.h" +#include "windowing/GraphicContext.h" + +using namespace KODI; +using namespace GAME; + +CDialogGameVideoSelect::CDialogGameVideoSelect(int windowId) + : CGUIDialog(windowId, "DialogSelect.xml"), + m_viewControl(new CGUIViewControl), + m_vecItems(new CFileItemList) +{ + // Initialize CGUIWindow + m_loadType = KEEP_IN_MEMORY; +} + +CDialogGameVideoSelect::~CDialogGameVideoSelect() = default; + +bool CDialogGameVideoSelect::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_INIT: + { + RegisterDialog(); + + // Don't init this dialog if we aren't playing a game + if (!m_gameVideoHandle || !m_gameVideoHandle->IsPlayingGame()) + return false; + + break; + } + case GUI_MSG_WINDOW_DEINIT: + { + UnregisterDialog(); + + break; + } + case GUI_MSG_SETFOCUS: + { + const int controlId = message.GetControlId(); + if (m_viewControl->HasControl(controlId) && m_viewControl->GetCurrentControl() != controlId) + { + m_viewControl->SetFocused(); + return true; + } + break; + } + case GUI_MSG_CLICKED: + { + const int actionId = message.GetParam1(); + if (actionId == ACTION_SELECT_ITEM || actionId == ACTION_MOUSE_LEFT_CLICK) + { + const int controlId = message.GetSenderId(); + if (m_viewControl->HasControl(controlId)) + { + if (OnClickAction()) + return true; + } + } + else if (actionId == ACTION_CONTEXT_MENU || actionId == ACTION_MOUSE_RIGHT_CLICK) + { + const int controlId = message.GetSenderId(); + if (m_viewControl->HasControl(controlId)) + { + if (OnMenuAction()) + return true; + } + } + else if (actionId == ACTION_CREATE_BOOKMARK) + { + const int controlId = message.GetSenderId(); + if (m_viewControl->HasControl(controlId)) + { + if (OnOverwriteAction()) + return true; + } + } + else if (actionId == ACTION_RENAME_ITEM) + { + const int controlId = message.GetSenderId(); + if (m_viewControl->HasControl(controlId)) + { + if (OnRenameAction()) + return true; + } + } + else if (actionId == ACTION_DELETE_ITEM) + { + const int controlId = message.GetSenderId(); + if (m_viewControl->HasControl(controlId)) + { + if (OnDeleteAction()) + return true; + } + } + + break; + } + case GUI_MSG_REFRESH_LIST: + { + RefreshList(); + break; + } + default: + break; + } + + return CGUIDialog::OnMessage(message); +} + +void CDialogGameVideoSelect::FrameMove() +{ + CGUIBaseContainer* thumbs = dynamic_cast<CGUIBaseContainer*>(GetControl(CONTROL_VIDEO_THUMBS)); + if (thumbs != nullptr) + OnItemFocus(thumbs->GetSelectedItem()); + + CGUIDialog::FrameMove(); +} + +void CDialogGameVideoSelect::OnWindowLoaded() +{ + CGUIDialog::OnWindowLoaded(); + + m_viewControl->SetParentWindow(GetID()); + m_viewControl->AddView(GetControl(CONTROL_VIDEO_THUMBS)); +} + +void CDialogGameVideoSelect::OnWindowUnload() +{ + m_viewControl->Reset(); + + CGUIDialog::OnWindowUnload(); +} + +void CDialogGameVideoSelect::OnInitWindow() +{ + PreInit(); + + CGUIDialog::OnInitWindow(); + + Update(); + + CGUIMessage msg(GUI_MSG_SETFOCUS, GetID(), CONTROL_VIDEO_THUMBS); + OnMessage(msg); + + std::string heading = GetHeading(); + SET_CONTROL_LABEL(CONTROL_VIDEO_HEADING, heading); + + FrameMove(); +} + +void CDialogGameVideoSelect::OnDeinitWindow(int nextWindowID) +{ + Clear(); + + CGUIDialog::OnDeinitWindow(nextWindowID); + + PostExit(); + + SaveSettings(); +} + +void CDialogGameVideoSelect::Update() +{ + //! @todo + // Lock our display, as this window is rendered from the player thread + // CServiceBroker::GetWinSystem()->GetGfxContext().Lock(); + + m_viewControl->SetCurrentView(DEFAULT_VIEW_ICONS); + + // Empty the list ready for population + Clear(); + + RefreshList(); + + // CServiceBroker::GetWinSystem()->GetGfxContext().Unlock(); +} + +void CDialogGameVideoSelect::Clear() +{ + m_viewControl->Clear(); + m_vecItems->Clear(); +} + +void CDialogGameVideoSelect::RefreshList() +{ + m_vecItems->Clear(); + + GetItems(*m_vecItems); + + m_viewControl->SetItems(*m_vecItems); + + auto focusedIndex = GetFocusedItem(); + m_viewControl->SetSelectedItem(focusedIndex); + OnItemFocus(focusedIndex); + + // Refresh the panel container + CGUIMessage message(GUI_MSG_REFRESH_THUMBS, GetID(), CONTROL_VIDEO_THUMBS); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message, GetID()); +} + +void CDialogGameVideoSelect::SaveSettings() +{ + CGameSettings& defaultSettings = CMediaSettings::GetInstance().GetDefaultGameSettings(); + CGameSettings& currentSettings = CMediaSettings::GetInstance().GetCurrentGameSettings(); + + if (defaultSettings != currentSettings) + { + defaultSettings = currentSettings; + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + } +} + +void CDialogGameVideoSelect::OnDescriptionChange(const std::string& description) +{ + CGUIMessage msg(GUI_MSG_LABEL_SET, GetID(), CONTROL_VIDEO_THUMBS); + msg.SetLabel(description); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, GetID()); +} + +void CDialogGameVideoSelect::RegisterDialog() +{ + m_gameVideoHandle = CServiceBroker::GetGameRenderManager().RegisterDialog(*this); +} + +void CDialogGameVideoSelect::UnregisterDialog() +{ + m_gameVideoHandle.reset(); +} diff --git a/xbmc/games/dialogs/osd/DialogGameVideoSelect.h b/xbmc/games/dialogs/osd/DialogGameVideoSelect.h new file mode 100644 index 0000000..c14c94e --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameVideoSelect.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +#include <memory> + +class CFileItemList; +class CGUIViewControl; + +namespace KODI +{ +namespace RETRO +{ +class CGUIGameVideoHandle; +} + +namespace GAME +{ +class CDialogGameVideoSelect : public CGUIDialog +{ +public: + ~CDialogGameVideoSelect() override; + + // implementation of CGUIControl via CGUIDialog + bool OnMessage(CGUIMessage& message) override; + + // implementation of CGUIWindow via CGUIDialog + void FrameMove() override; + void OnDeinitWindow(int nextWindowID) override; + +protected: + CDialogGameVideoSelect(int windowId); + + // implementation of CGUIWindow via CGUIDialog + void OnWindowUnload() override; + void OnWindowLoaded() override; + void OnInitWindow() override; + + // Video select interface + virtual std::string GetHeading() = 0; + virtual void PreInit() = 0; + virtual void GetItems(CFileItemList& items) = 0; + virtual void OnItemFocus(unsigned int index) = 0; + virtual unsigned int GetFocusedItem() const = 0; + virtual void PostExit() = 0; + // override this to do something when an item is selected + virtual bool OnClickAction() { return false; } + // override this to do something when an item's context menu is opened + virtual bool OnMenuAction() { return false; } + // override this to do something when an item is overwritten with a new savestate + virtual bool OnOverwriteAction() { return false; } + // override this to do something when an item is renamed + virtual bool OnRenameAction() { return false; } + // override this to do something when an item is deleted + virtual bool OnDeleteAction() { return false; } + + // GUI functions + void RefreshList(); + void OnDescriptionChange(const std::string& description); + + std::shared_ptr<RETRO::CGUIGameVideoHandle> m_gameVideoHandle; + +private: + void Update(); + void Clear(); + + void SaveSettings(); + + void RegisterDialog(); + void UnregisterDialog(); + + std::unique_ptr<CGUIViewControl> m_viewControl; + std::unique_ptr<CFileItemList> m_vecItems; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/dialogs/osd/DialogGameVolume.cpp b/xbmc/games/dialogs/osd/DialogGameVolume.cpp new file mode 100644 index 0000000..8467269 --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameVolume.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DialogGameVolume.h" + +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationVolumeHandling.h" +#include "dialogs/GUIDialogVolumeBar.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIDialog.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUISliderControl.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/WindowIDs.h" +#include "interfaces/AnnouncementManager.h" +#include "utils/Variant.h" + +#include <cmath> + +using namespace KODI; +using namespace GAME; + +#define CONTROL_LABEL 12 //! @todo Remove me + +CDialogGameVolume::CDialogGameVolume() +{ + // Initialize CGUIWindow + SetID(WINDOW_DIALOG_GAME_VOLUME); + m_loadType = KEEP_IN_MEMORY; +} + +bool CDialogGameVolume::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_STATE_CHANGED: + { + int controlId = message.GetControlId(); + if (controlId == GetID()) + { + OnStateChanged(); + return true; + } + + break; + } + default: + break; + } + + return CGUIDialogSlider::OnMessage(message); +} + +void CDialogGameVolume::OnInitWindow() +{ + m_volumePercent = m_oldVolumePercent = GetVolumePercent(); + + CGUIDialogSlider::OnInitWindow(); + + // Set slider parameters + SetModalityType(DialogModalityType::MODAL); + SetSlider(GetLabel(), GetVolumePercent(), VOLUME_MIN, VOLUME_DELTA, VOLUME_MAX, this, nullptr); + + SET_CONTROL_HIDDEN(CONTROL_LABEL); + + CGUIDialogVolumeBar* dialogVolumeBar = dynamic_cast<CGUIDialogVolumeBar*>( + CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_VOLUME_BAR)); + if (dialogVolumeBar != nullptr) + dialogVolumeBar->RegisterCallback(this); + + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); +} + +void CDialogGameVolume::OnDeinitWindow(int nextWindowID) +{ + CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this); + + CGUIDialogVolumeBar* dialogVolumeBar = dynamic_cast<CGUIDialogVolumeBar*>( + CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_VOLUME_BAR)); + if (dialogVolumeBar != nullptr) + dialogVolumeBar->UnregisterCallback(this); + + CGUIDialogSlider::OnDeinitWindow(nextWindowID); +} + +void CDialogGameVolume::OnSliderChange(void* data, CGUISliderControl* slider) +{ + const float volumePercent = slider->GetFloatValue(); + + if (std::fabs(volumePercent - m_volumePercent) > 0.1f) + { + m_volumePercent = volumePercent; + auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + appVolume->SetVolume(volumePercent, true); + } +} + +bool CDialogGameVolume::IsShown() const +{ + return m_active; +} + +void CDialogGameVolume::Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) +{ + if (flag == ANNOUNCEMENT::Application && message == "OnVolumeChanged") + { + const float volumePercent = static_cast<float>(data["volume"].asDouble()); + + if (std::fabs(volumePercent - m_volumePercent) > 0.1f) + { + m_volumePercent = volumePercent; + + CGUIMessage msg(GUI_MSG_STATE_CHANGED, GetID(), GetID()); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + } + } +} + +void CDialogGameVolume::OnStateChanged() +{ + if (m_volumePercent != m_oldVolumePercent) + { + m_oldVolumePercent = m_volumePercent; + SetSlider(GetLabel(), m_volumePercent, VOLUME_MIN, VOLUME_DELTA, VOLUME_MAX, this, nullptr); + } +} + +float CDialogGameVolume::GetVolumePercent() const +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + return appVolume->GetVolumePercent(); +} + +std::string CDialogGameVolume::GetLabel() +{ + return g_localizeStrings.Get(13376); // "Volume" +} diff --git a/xbmc/games/dialogs/osd/DialogGameVolume.h b/xbmc/games/dialogs/osd/DialogGameVolume.h new file mode 100644 index 0000000..a32339d --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogGameVolume.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "dialogs/GUIDialogSlider.h" +#include "dialogs/IGUIVolumeBarCallback.h" +#include "guilib/ISliderCallback.h" +#include "interfaces/IAnnouncer.h" + +#include <set> + +namespace KODI +{ + +namespace GAME +{ +class CDialogGameVolume : public CGUIDialogSlider, // GUI interface + public ISliderCallback, // GUI callback + public IGUIVolumeBarCallback, // Volume bar dialog callback + public ANNOUNCEMENT::IAnnouncer // Application callback +{ +public: + CDialogGameVolume(); + ~CDialogGameVolume() override = default; + + // implementation of CGUIControl via CGUIDialogSlider + bool OnMessage(CGUIMessage& message) override; + + // implementation of CGUIWindow via CGUIDialogSlider + void OnDeinitWindow(int nextWindowID) override; + + // implementation of ISliderCallback + void OnSliderChange(void* data, CGUISliderControl* slider) override; + + // implementation of IGUIVolumeBarCallback + bool IsShown() const override; + + // implementation of IAnnouncer + void Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) override; + +protected: + // implementation of CGUIWindow via CGUIDialogSlider + void OnInitWindow() override; + +private: + /*! + * \brief Call when state change message is received + */ + void OnStateChanged(); + + /*! + * \brief Get the volume of the first callback + * + * \return The volume, as a fraction of maximum volume + */ + float GetVolumePercent() const; + + /*! + * \brief Get the volume bar label + */ + static std::string GetLabel(); + + // Volume parameters + const float VOLUME_MIN = 0.0f; + const float VOLUME_DELTA = 10.0f; + const float VOLUME_MAX = 100.0f; + float m_volumePercent = 100.0f; + float m_oldVolumePercent = 100.0f; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/dialogs/osd/DialogInGameSaves.cpp b/xbmc/games/dialogs/osd/DialogInGameSaves.cpp new file mode 100644 index 0000000..9f66fd5 --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogInGameSaves.cpp @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2020-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DialogInGameSaves.h" + +#include "ServiceBroker.h" +#include "URL.h" +#include "XBDateTime.h" +#include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h" +#include "cores/RetroPlayer/guibridge/GUIGameSettingsHandle.h" +#include "cores/RetroPlayer/guicontrols/GUIGameControl.h" +#include "cores/RetroPlayer/playback/IPlayback.h" +#include "cores/RetroPlayer/savestates/ISavestate.h" +#include "cores/RetroPlayer/savestates/SavestateDatabase.h" +#include "dialogs/GUIDialogContextMenu.h" +#include "dialogs/GUIDialogOK.h" +#include "dialogs/GUIDialogYesNo.h" +#include "games/dialogs/DialogGameDefines.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIMessage.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/WindowIDs.h" +#include "settings/GameSettings.h" +#include "settings/MediaSettings.h" +#include "utils/log.h" + +using namespace KODI; +using namespace GAME; +using namespace RETRO; + +namespace +{ +CFileItemPtr CreateNewSaveItem() +{ + CFileItemPtr item = std::make_shared<CFileItem>(g_localizeStrings.Get(15314)); // "Save" + + // A nonexistent path ensures a gamewindow control won't render any pixels + item->SetPath(NO_PIXEL_DATA); + item->SetArt("icon", "DefaultAddSource.png"); + item->SetProperty(SAVESTATE_CAPTION, + g_localizeStrings.Get(15315)); // "Save progress to a new save file" + + return item; +} +} // namespace + +CDialogInGameSaves::CDialogInGameSaves() + : CDialogGameVideoSelect(WINDOW_DIALOG_IN_GAME_SAVES), m_newSaveItem(CreateNewSaveItem()) +{ +} + +bool CDialogInGameSaves::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_REFRESH_THUMBS: + { + if (message.GetControlId() == GetID()) + { + const std::string& itemPath = message.GetStringParam(); + CGUIListItemPtr itemInfo = message.GetItem(); + + if (!itemPath.empty()) + { + OnItemRefresh(itemPath, std::move(itemInfo)); + } + else + { + InitSavedGames(); + RefreshList(); + } + + return true; + } + break; + } + default: + break; + } + + return CDialogGameVideoSelect::OnMessage(message); +} + +std::string CDialogInGameSaves::GetHeading() +{ + return g_localizeStrings.Get(35249); // "Save / Load" +} + +void CDialogInGameSaves::PreInit() +{ + InitSavedGames(); +} + +void CDialogInGameSaves::InitSavedGames() +{ + m_savestateItems.Clear(); + + auto gameSettings = CServiceBroker::GetGameRenderManager().RegisterGameSettingsDialog(); + + CSavestateDatabase db; + db.GetSavestatesNav(m_savestateItems, gameSettings->GetPlayingGame(), + gameSettings->GameClientID()); + + m_savestateItems.Sort(SortByDate, SortOrderDescending); +} + +void CDialogInGameSaves::GetItems(CFileItemList& items) +{ + items.Add(m_newSaveItem); + std::for_each(m_savestateItems.cbegin(), m_savestateItems.cend(), + [&items](const auto& item) { items.Add(item); }); +} + +void CDialogInGameSaves::OnItemFocus(unsigned int index) +{ + if (static_cast<int>(index) < 1 + m_savestateItems.Size()) + m_focusedItemIndex = index; +} + +unsigned int CDialogInGameSaves::GetFocusedItem() const +{ + return m_focusedControl; +} + +void CDialogInGameSaves::OnItemRefresh(const std::string& itemPath, CGUIListItemPtr itemInfo) +{ + // Turn the message params into a savestate item + CFileItemPtr item = TranslateMessageItem(itemPath, std::move(itemInfo)); + if (item) + { + // Look up existing savestate by path + auto it = + std::find_if(m_savestateItems.cbegin(), m_savestateItems.cend(), + [&itemPath](const CFileItemPtr& item) { return item->GetPath() == itemPath; }); + + // Update savestate or add a new one + if (it != m_savestateItems.cend()) + **it = std::move(*item); + else + m_savestateItems.AddFront(std::move(item), 0); + + RefreshList(); + } +} + +void CDialogInGameSaves::PostExit() +{ + m_savestateItems.Clear(); +} + +bool CDialogInGameSaves::OnClickAction() +{ + if (static_cast<int>(m_focusedItemIndex) < 1 + m_savestateItems.Size()) + { + if (m_focusedItemIndex <= 0) + { + OnNewSave(); + return true; + } + else + { + CFileItemPtr focusedItem = m_savestateItems[m_focusedItemIndex - 1]; + if (focusedItem) + { + OnLoad(*focusedItem); + return true; + } + } + } + + return false; +} + +bool CDialogInGameSaves::OnMenuAction() +{ + // Start at index 1 to account for leading "Save" item + if (1 <= m_focusedItemIndex && static_cast<int>(m_focusedItemIndex) < 1 + m_savestateItems.Size()) + { + CFileItemPtr focusedItem = m_savestateItems[m_focusedItemIndex - 1]; + if (focusedItem) + { + CContextButtons buttons; + + buttons.Add(0, 13206); // "Overwrite" + buttons.Add(1, 118); // "Rename" + buttons.Add(2, 117); // "Delete" + + const int index = CGUIDialogContextMenu::Show(buttons); + + if (index == 0) + OnOverwrite(*focusedItem); + if (index == 1) + OnRename(*focusedItem); + else if (index == 2) + OnDelete(*focusedItem); + + return true; + } + } + + return false; +} + +bool CDialogInGameSaves::OnOverwriteAction() +{ + // Start at index 1 to account for leading "Save" item + if (1 <= m_focusedItemIndex && static_cast<int>(m_focusedItemIndex) < 1 + m_savestateItems.Size()) + { + CFileItemPtr focusedItem = m_savestateItems[m_focusedItemIndex - 1]; + if (focusedItem) + { + OnOverwrite(*focusedItem); + return true; + } + } + + return false; +} + +bool CDialogInGameSaves::OnRenameAction() +{ + // Start at index 1 to account for leading "Save" item + if (1 <= m_focusedItemIndex && static_cast<int>(m_focusedItemIndex) < 1 + m_savestateItems.Size()) + { + CFileItemPtr focusedItem = m_savestateItems[m_focusedItemIndex - 1]; + if (focusedItem) + { + OnRename(*focusedItem); + return true; + } + } + + return false; +} + +bool CDialogInGameSaves::OnDeleteAction() +{ + // Start at index 1 to account for leading "Save" item + if (1 <= m_focusedItemIndex && static_cast<int>(m_focusedItemIndex) < 1 + m_savestateItems.Size()) + { + CFileItemPtr focusedItem = m_savestateItems[m_focusedItemIndex - 1]; + if (focusedItem) + { + OnDelete(*focusedItem); + return true; + } + } + + return false; +} + +void CDialogInGameSaves::OnNewSave() +{ + auto gameSettings = CServiceBroker::GetGameRenderManager().RegisterGameSettingsDialog(); + + const std::string savestatePath = gameSettings->CreateSavestate(false); + if (savestatePath.empty()) + { + // "Error" + // "An unknown error has occurred." + CGUIDialogOK::ShowAndGetInput(257, 24071); + return; + } + + // Create a simulated savestate to update the GUI faster. We will be notified + // of the real savestate info via OnMessage() when the savestate creation + // completes. + auto savestate = RETRO::CSavestateDatabase::AllocateSavestate(); + + savestate->SetType(SAVE_TYPE::MANUAL); + savestate->SetCreated(CDateTime::GetUTCDateTime()); + + savestate->Finalize(); + + CFileItemPtr item = std::make_shared<CFileItem>(); + CSavestateDatabase::GetSavestateItem(*savestate, savestatePath, *item); + + m_savestateItems.AddFront(std::move(item), 0); + + RefreshList(); +} + +void CDialogInGameSaves::OnLoad(CFileItem& focusedItem) +{ + auto gameSettings = CServiceBroker::GetGameRenderManager().RegisterGameSettingsDialog(); + + // Load savestate + if (gameSettings->LoadSavestate(focusedItem.GetPath())) + { + // Close OSD on successful load + gameSettings->CloseOSD(); + } + else + { + // "Error" + // "An unknown error has occurred." + CGUIDialogOK::ShowAndGetInput(257, 24071); + } +} + +void CDialogInGameSaves::OnOverwrite(CFileItem& focusedItem) +{ + std::string savestatePath = focusedItem.GetPath(); + if (savestatePath.empty()) + return; + + auto gameSettings = CServiceBroker::GetGameRenderManager().RegisterGameSettingsDialog(); + + // Update savestate + if (gameSettings->UpdateSavestate(savestatePath)) + { + // Create a simulated savestate to update the GUI faster. We will be + // notified of the real savestate info via OnMessage() when the + // overwriting completes. + auto savestate = RETRO::CSavestateDatabase::AllocateSavestate(); + + savestate->SetType(SAVE_TYPE::MANUAL); + savestate->SetLabel(focusedItem.GetProperty(SAVESTATE_LABEL).asString()); + savestate->SetCaption(focusedItem.GetProperty(SAVESTATE_CAPTION).asString()); + savestate->SetCreated(CDateTime::GetUTCDateTime()); + + savestate->Finalize(); + + CSavestateDatabase::GetSavestateItem(*savestate, savestatePath, focusedItem); + + RefreshList(); + } + else + { + // Log an error and notify the user + CLog::Log(LOGERROR, "Failed to overwrite savestate at {}", CURL::GetRedacted(savestatePath)); + + // "Error" + // "An unknown error has occurred." + CGUIDialogOK::ShowAndGetInput(257, 24071); + } +} + +void CDialogInGameSaves::OnRename(CFileItem& focusedItem) +{ + const std::string& savestatePath = focusedItem.GetPath(); + if (savestatePath.empty()) + return; + + RETRO::CSavestateDatabase db; + + std::string label; + + std::unique_ptr<RETRO::ISavestate> savestate = RETRO::CSavestateDatabase::AllocateSavestate(); + if (db.GetSavestate(savestatePath, *savestate)) + label = savestate->Label(); + + // "Enter new filename" + if (CGUIKeyboardFactory::ShowAndGetInput(label, CVariant{g_localizeStrings.Get(16013)}, true) && + label != savestate->Label()) + { + std::unique_ptr<RETRO::ISavestate> newSavestate = db.RenameSavestate(savestatePath, label); + if (newSavestate) + { + RETRO::CSavestateDatabase::GetSavestateItem(*newSavestate, savestatePath, focusedItem); + + RefreshList(); + } + else + { + // "Error" + // "An unknown error has occurred." + CGUIDialogOK::ShowAndGetInput(257, 24071); + } + } +} + +void CDialogInGameSaves::OnDelete(CFileItem& focusedItem) +{ + // "Confirm delete" + // "Would you like to delete the selected file(s)?[CR]Warning - this action can't be undone!" + if (CGUIDialogYesNo::ShowAndGetInput(CVariant{122}, CVariant{125})) + { + RETRO::CSavestateDatabase db; + if (db.DeleteSavestate(focusedItem.GetPath())) + { + m_savestateItems.Remove(&focusedItem); + + RefreshList(); + + auto gameSettings = CServiceBroker::GetGameRenderManager().RegisterGameSettingsDialog(); + gameSettings->FreeSavestateResources(focusedItem.GetPath()); + } + else + { + // "Error" + // "An unknown error has occurred." + CGUIDialogOK::ShowAndGetInput(257, 24071); + } + } +} + +CFileItemPtr CDialogInGameSaves::TranslateMessageItem(const std::string& messagePath, + CGUIListItemPtr messageItem) +{ + CFileItemPtr item; + + if (messageItem && messageItem->IsFileItem()) + item = std::static_pointer_cast<CFileItem>(messageItem); + else if (messageItem) + item = std::make_shared<CFileItem>(*messageItem); + else if (!messagePath.empty()) + { + item = std::make_shared<CFileItem>(); + + // Load savestate if no item info was given + auto savestate = RETRO::CSavestateDatabase::AllocateSavestate(); + RETRO::CSavestateDatabase db; + if (db.GetSavestate(messagePath, *savestate)) + RETRO::CSavestateDatabase::GetSavestateItem(*savestate, messagePath, *item); + else + item.reset(); + } + + return item; +} diff --git a/xbmc/games/dialogs/osd/DialogInGameSaves.h b/xbmc/games/dialogs/osd/DialogInGameSaves.h new file mode 100644 index 0000000..6f98f94 --- /dev/null +++ b/xbmc/games/dialogs/osd/DialogInGameSaves.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DialogGameVideoSelect.h" +#include "FileItem.h" +#include "guilib/GUIListItem.h" + +#include <string> + +namespace KODI +{ +namespace GAME +{ +class CDialogInGameSaves : public CDialogGameVideoSelect +{ +public: + CDialogInGameSaves(); + ~CDialogInGameSaves() override = default; + + // implementation of CGUIControl via CDialogGameVideoSelect + bool OnMessage(CGUIMessage& message) override; + +protected: + // implementation of CDialogGameVideoSelect + std::string GetHeading() override; + void PreInit() override; + void GetItems(CFileItemList& items) override; + void OnItemFocus(unsigned int index) override; + unsigned int GetFocusedItem() const override; + void PostExit() override; + bool OnClickAction() override; + bool OnMenuAction() override; + bool OnOverwriteAction() override; + bool OnRenameAction() override; + bool OnDeleteAction() override; + + void OnNewSave(); + void OnLoad(CFileItem& focusedItem); + void OnOverwrite(CFileItem& focusedItem); + void OnRename(CFileItem& focusedItem); + void OnDelete(CFileItem& focusedItem); + +private: + void InitSavedGames(); + void OnItemRefresh(const std::string& itemPath, CGUIListItemPtr itemInfo); + + /*! + * \brief Translates the GUI list item received in a GUI message into a + * CFileItem with savestate properties + * + * When a savestate is overwritten, we optimistically populate the GUI list + * with a simulated savestate for immediate user feedback. Later (about a + * quarter second) a message arrives with the real savestate info. + * + * \param messagePath The savestate path, pass as the message's string param + * \param messageItem The savestate info, if known, or empty if unknown + * + * If messageItem is empty, the savestate will be loaded from disk, which + * is potentially expensive. + * + * \return A savestate item for the GUI, or empty if no savestate information + * can be obtained + */ + static CFileItemPtr TranslateMessageItem(const std::string& messagePath, + CGUIListItemPtr messageItem); + + CFileItemList m_savestateItems; + const CFileItemPtr m_newSaveItem; + unsigned int m_focusedItemIndex = false; +}; +} // namespace GAME +} // namespace KODI |