1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +02:00

Extracted window management from CMT to new class

This commit is contained in:
Ivan Savenko
2023-04-30 01:03:50 +03:00
parent 18a66a4376
commit c688411bab
9 changed files with 522 additions and 497 deletions

View File

@@ -7,49 +7,34 @@
* Full text of license available in license.txt file, in main folder * Full text of license available in license.txt file, in main folder
* *
*/ */
// CMT.cpp : Defines the entry point for the console application. // CMT.cpp : Defines the entry point for the console application.
//
#include "StdInc.h" #include "StdInc.h"
#include "CMT.h"
#include "CGameInfo.h" #include "CGameInfo.h"
#include "mainmenu/CMainMenu.h" #include "mainmenu/CMainMenu.h"
#include "lobby/CSelectionBase.h" #include "mainmenu/CPrologEpilogVideo.h"
#include "windows/CCastleInterface.h"
#include "gui/CursorHandler.h" #include "gui/CursorHandler.h"
#include "CPlayerInterface.h" #include "CPlayerInterface.h"
#include "CVideoHandler.h" #include "CVideoHandler.h"
#include "CMusicHandler.h" #include "CMusicHandler.h"
#include "Client.h"
#include "gui/CGuiHandler.h" #include "gui/CGuiHandler.h"
#include "CServerHandler.h" #include "CServerHandler.h"
#include "gui/NotificationHandler.h" #include "gui/NotificationHandler.h"
#include "ClientCommandManager.h" #include "ClientCommandManager.h"
#include "windows/CMessage.h" #include "windows/CMessage.h"
#include "renderSDL/SDL_Extensions.h" #include "renderSDL/WindowHandler.h"
#include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/FileStream.h"
#include "../lib/CConsoleHandler.h" #include "../lib/CConsoleHandler.h"
#include "../lib/CGameState.h"
#include "../lib/CBuildingHandler.h"
#include "../CCallback.h"
#include "../lib/CHeroHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CGeneralTextHandler.h" #include "../lib/CGeneralTextHandler.h"
#include "../lib/serializer/BinaryDeserializer.h"
#include "../lib/serializer/BinarySerializer.h"
#include "../lib/VCMIDirs.h" #include "../lib/VCMIDirs.h"
#include "../lib/NetPacks.h" #include "../lib/mapping/CCampaignHandler.h"
#include "../lib/CModHandler.h"
#include "../lib/CTownHandler.h"
#include "../lib/logging/CBasicLogConfigurator.h" #include "../lib/logging/CBasicLogConfigurator.h"
#include "../lib/CPlayerState.h"
#include "../lib/serializer/Connection.h"
#include <boost/asio.hpp>
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
#include "mainmenu/CPrologEpilogVideo.h"
#include <vstd/StringUtils.h> #include <vstd/StringUtils.h>
#include <SDL.h> #include <SDL.h>
@@ -60,8 +45,6 @@
#include "../lib/CAndroidVMHelper.h" #include "../lib/CAndroidVMHelper.h"
#endif #endif
#include "CMT.h"
#if __MINGW32__ #if __MINGW32__
#undef main #undef main
#endif #endif
@@ -70,11 +53,8 @@ namespace po = boost::program_options;
namespace po_style = boost::program_options::command_line_style; namespace po_style = boost::program_options::command_line_style;
namespace bfs = boost::filesystem; namespace bfs = boost::filesystem;
std::string NAME_AFFIX = "client";
std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
CGuiHandler GH; CGuiHandler GH;
int preferredDriverIndex = -1;
SDL_Window * mainWindow = nullptr; SDL_Window * mainWindow = nullptr;
SDL_Renderer * mainRenderer = nullptr; SDL_Renderer * mainRenderer = nullptr;
SDL_Texture * screenTexture = nullptr; SDL_Texture * screenTexture = nullptr;
@@ -95,19 +75,11 @@ static po::variables_map vm;
#ifndef VCMI_IOS #ifndef VCMI_IOS
void processCommand(const std::string &message); void processCommand(const std::string &message);
#endif #endif
static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayIndex, bool resetVideo=true);
void playIntro(); void playIntro();
static void mainLoop(); static void mainLoop();
static CBasicLogConfigurator *logConfig; static CBasicLogConfigurator *logConfig;
#ifndef VCMI_WINDOWS
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <getopt.h>
#endif
void init() void init()
{ {
CStopWatch tmh; CStopWatch tmh;
@@ -139,17 +111,6 @@ static void prog_help(const po::options_description &opts)
std::cout << opts; std::cout << opts;
} }
static void SDLLogCallback(void* userdata,
int category,
SDL_LogPriority priority,
const char* message)
{
//todo: convert SDL log priority to vcmi log priority
//todo: make separate log domain for SDL
logGlobal->debug("SDL(category %d; priority %d) %s", category, priority, message);
}
#if defined(VCMI_WINDOWS) && !defined(__GNUC__) && defined(VCMI_WITH_DEBUG_CONSOLE) #if defined(VCMI_WINDOWS) && !defined(__GNUC__) && defined(VCMI_WITH_DEBUG_CONSOLE)
int wmain(int argc, wchar_t* argv[]) int wmain(int argc, wchar_t* argv[])
#elif defined(VCMI_MOBILE) #elif defined(VCMI_MOBILE)
@@ -255,7 +216,7 @@ int main(int argc, char * argv[])
const bfs::path logPath = VCMIDirs::get().userLogsPath() / "VCMI_Client_log.txt"; const bfs::path logPath = VCMIDirs::get().userLogsPath() / "VCMI_Client_log.txt";
logConfig = new CBasicLogConfigurator(logPath, console); logConfig = new CBasicLogConfigurator(logPath, console);
logConfig->configureDefault(); logConfig->configureDefault();
logGlobal->info(NAME); logGlobal->info("Starting client of '%s'", GameConstants::VCMI_VERSION);
logGlobal->info("Creating console and configuring logger: %d ms", pomtime.getDiff()); logGlobal->info("Creating console and configuring logger: %d ms", pomtime.getDiff());
logGlobal->info("The log file will be saved to %s", logPath); logGlobal->info("The log file will be saved to %s", logPath);
@@ -340,68 +301,9 @@ int main(int argc, char * argv[])
srand ( (unsigned int)time(nullptr) ); srand ( (unsigned int)time(nullptr) );
const JsonNode& video = settings["video"];
const JsonNode& res = video["screenRes"];
//something is really wrong...
if (res["width"].Float() < 100 || res["height"].Float() < 100)
{
logGlobal->error("Fatal error: failed to load settings!");
logGlobal->error("Possible reasons:");
logGlobal->error("\tCorrupted local configuration file at %s/settings.json", VCMIDirs::get().userConfigPath());
logGlobal->error("\tMissing or corrupted global configuration file at %s/schemas/settings.json", VCMIDirs::get().userConfigPath());
logGlobal->error("VCMI will now exit...");
exit(EXIT_FAILURE);
}
if(!settings["session"]["headless"].Bool()) if(!settings["session"]["headless"].Bool())
{
if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_AUDIO|SDL_INIT_NOPARACHUTE))
{
logGlobal->error("Something was wrong: %s", SDL_GetError());
exit(-1);
}
#ifdef VCMI_ANDROID
// manually setting egl pixel format, as a possible solution for sdl2<->android problem
// https://bugzilla.libsdl.org/show_bug.cgi?id=2291
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
#endif // VCMI_ANDROID
//(!)init here AFTER SDL_Init() while using SDL for FPS management
GH.init(); GH.init();
SDL_LogSetOutputFunction(&SDLLogCallback, nullptr);
int driversCount = SDL_GetNumRenderDrivers();
std::string preferredDriverName = video["driver"].String();
logGlobal->info("Found %d render drivers", driversCount);
for(int it = 0; it < driversCount; it++)
{
SDL_RendererInfo info;
SDL_GetRenderDriverInfo(it,&info);
std::string driverName(info.name);
if(!preferredDriverName.empty() && driverName == preferredDriverName)
{
preferredDriverIndex = it;
logGlobal->info("\t%s (active)", driverName);
}
else
logGlobal->info("\t%s", driverName);
}
setScreenRes((int)res["width"].Float(), (int)res["height"].Float(), (int)video["bitsPerPixel"].Float(), video["fullscreen"].Bool(), (int)video["displayIndex"].Float());
logGlobal->info("\tInitializing screen: %d ms", pomtime.getDiff());
}
CCS = new CClientState(); CCS = new CClientState();
CGI = new CGameInfo(); //contains all global informations about game (texts, lodHandlers, map handler etc.) CGI = new CGameInfo(); //contains all global informations about game (texts, lodHandlers, map handler etc.)
CSH = new CServerHandler(); CSH = new CServerHandler();
@@ -487,7 +389,6 @@ int main(int argc, char * argv[])
CCS->curh->show(); CCS->curh->show();
} }
logGlobal->info("Initialization of VCMI (together): %d ms", total.getDiff()); logGlobal->info("Initialization of VCMI (together): %d ms", total.getDiff());
session["autoSkip"].Bool() = vm.count("autoSkip"); session["autoSkip"].Bool() = vm.count("autoSkip");
@@ -577,361 +478,6 @@ void playIntro()
} }
} }
#if !defined(VCMI_MOBILE)
static bool checkVideoMode(int monitorIndex, int w, int h)
{
//we only check that our desired window size fits on screen
SDL_DisplayMode mode;
if (0 != SDL_GetDesktopDisplayMode(monitorIndex, &mode))
{
logGlobal->error("SDL_GetDesktopDisplayMode failed");
logGlobal->error(SDL_GetError());
return false;
}
logGlobal->info("Check display mode: requested %d x %d; available up to %d x %d ", w, h, mode.w, mode.h);
if (!mode.w || !mode.h || (w <= mode.w && h <= mode.h))
{
return true;
}
return false;
}
#endif
static void cleanupRenderer()
{
screenBuf = nullptr; //it`s a link - just nullify
if(nullptr != screen2)
{
SDL_FreeSurface(screen2);
screen2 = nullptr;
}
if(nullptr != screen)
{
SDL_FreeSurface(screen);
screen = nullptr;
}
if(nullptr != screenTexture)
{
SDL_DestroyTexture(screenTexture);
screenTexture = nullptr;
}
}
static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIndex)
{
// VCMI will only work with 2 or 4 bytes per pixel
vstd::amax(bpp, 16);
vstd::amin(bpp, 32);
if(bpp>16)
bpp = 32;
if(displayIndex < 0)
{
if (mainWindow != nullptr)
displayIndex = SDL_GetWindowDisplayIndex(mainWindow);
if (displayIndex < 0)
displayIndex = 0;
}
#if defined(VCMI_MOBILE)
SDL_GetWindowSize(mainWindow, &w, &h);
#else
if(!checkVideoMode(displayIndex, w, h))
{
logGlobal->error("Error: SDL says that %dx%d resolution is not available!", w, h);
}
#endif
bool bufOnScreen = (screenBuf == screen);
bool realFullscreen = settings["video"]["realFullscreen"].Bool();
/* match best rendering resolution */
int renderWidth = 0, renderHeight = 0;
auto aspectRatio = (float)w / (float)h;
auto minDiff = 10.f;
// TODO: CONFIGURABLE ADVMAP
static const std::vector<Point> supportedResolutions = {
{ 1280, 720 }
};
for (const auto& pair : supportedResolutions)
{
int pWidth = pair.x;
int pHeight = pair.y;
/* filter out resolution which is larger than window */
if (pWidth > w || pHeight > h)
{
continue;
}
auto ratio = (float)pWidth / (float)pHeight;
auto diff = fabs(aspectRatio - ratio);
/* select closest aspect ratio */
if (diff < minDiff)
{
renderWidth = pWidth;
renderHeight = pHeight;
minDiff = diff;
}
/* select largest resolution meets prior conditions.
* since there are resolutions like 1366x768(not exactly 16:9), a deviation of 0.005 is allowed. */
else if (fabs(diff - minDiff) < 0.005f && pWidth > renderWidth)
{
renderWidth = pWidth;
renderHeight = pHeight;
}
}
if (renderWidth == 0)
{
// no matching resolution for upscaling - complain & fallback to default resolution.
logGlobal->error("Failed to match rendering resolution for %dx%d!", w, h);
Settings newRes = settings.write["video"]["screenRes"];
w = supportedResolutions.front().x;
h = supportedResolutions.front().y;
newRes["width"].Float() = w;
newRes["height"].Float() = h;
logGlobal->error("Falling back to %dx%d", w, h);
renderWidth = w;
renderHeight = h;
}
else
{
logGlobal->info("Set logical rendering resolution to %dx%d", renderWidth, renderHeight);
}
cleanupRenderer();
if(nullptr == mainWindow)
{
#if defined(VCMI_MOBILE)
auto createWindow = [displayIndex](uint32_t extraFlags) -> bool {
mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), 0, 0, SDL_WINDOW_FULLSCREEN | extraFlags);
return mainWindow != nullptr;
};
# ifdef VCMI_IOS
SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "1");
SDL_SetHint(SDL_HINT_RETURN_KEY_HIDES_IME, "1");
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
uint32_t windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI;
if(!createWindow(windowFlags | SDL_WINDOW_METAL))
{
logGlobal->warn("Metal unavailable, using OpenGLES");
createWindow(windowFlags);
}
# else
createWindow(0);
# endif // VCMI_IOS
// SDL on mobile doesn't do proper letterboxing, and will show an annoying flickering in the blank space in case you're not using the full screen estate
// That's why we need to make sure our width and height we'll use below have the same aspect ratio as the screen itself to ensure we fill the full screen estate
SDL_Rect screenRect;
if(SDL_GetDisplayBounds(0, &screenRect) == 0)
{
const auto screenWidth = screenRect.w;
const auto screenHeight = screenRect.h;
const auto aspect = static_cast<double>(screenWidth) / screenHeight;
logGlobal->info("Screen size and aspect ratio: %dx%d (%lf)", screenWidth, screenHeight, aspect);
if((double)w / aspect > (double)h)
{
h = (int)round((double)w / aspect);
}
else
{
w = (int)round((double)h * aspect);
}
logGlobal->info("Changing logical screen size to %dx%d", w, h);
}
else
{
logGlobal->error("Can't fix aspect ratio for screen");
}
#else
if(fullscreen)
{
if(realFullscreen)
mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), renderWidth, renderHeight, SDL_WINDOW_FULLSCREEN);
else //in windowed full-screen mode use desktop resolution
mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex),SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), 0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
}
else
{
mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex),SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), w, h, 0);
}
#endif // defined(VCMI_MOBILE)
if(nullptr == mainWindow)
{
throw std::runtime_error("Unable to create window\n");
}
//create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible
mainRenderer = SDL_CreateRenderer(mainWindow,preferredDriverIndex,0);
if(nullptr == mainRenderer)
{
throw std::runtime_error("Unable to create renderer\n");
}
SDL_RendererInfo info;
SDL_GetRendererInfo(mainRenderer, &info);
logGlobal->info("Created renderer %s", info.name);
}
else
{
#if !defined(VCMI_MOBILE)
if(fullscreen)
{
if(realFullscreen)
{
SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN);
SDL_DisplayMode mode;
SDL_GetDesktopDisplayMode(displayIndex, &mode);
mode.w = renderWidth;
mode.h = renderHeight;
SDL_SetWindowDisplayMode(mainWindow, &mode);
}
else
{
SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
}
SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex));
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
}
else
{
SDL_SetWindowFullscreen(mainWindow, 0);
SDL_SetWindowSize(mainWindow, w, h);
SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex));
}
#endif
}
if(!(fullscreen && realFullscreen))
{
SDL_RenderSetLogicalSize(mainRenderer, renderWidth, renderHeight);
//following line is bugged not only on android, do not re-enable without checking
//#ifndef VCMI_ANDROID
// // on android this stretches the game to fit the screen, not preserving aspect and apparently this also breaks coordinates scaling in mouse events
// SDL_RenderSetViewport(mainRenderer, nullptr);
//#endif
}
#ifdef VCMI_ENDIAN_BIG
int bmask = 0xff000000;
int gmask = 0x00ff0000;
int rmask = 0x0000ff00;
int amask = 0x000000ff;
#else
int bmask = 0x000000ff;
int gmask = 0x0000ff00;
int rmask = 0x00ff0000;
int amask = 0xFF000000;
#endif
screen = SDL_CreateRGBSurface(0,renderWidth,renderHeight,bpp,rmask,gmask,bmask,amask);
if(nullptr == screen)
{
logGlobal->error("Unable to create surface %dx%d with %d bpp: %s", renderWidth, renderHeight, bpp, SDL_GetError());
throw std::runtime_error("Unable to create surface");
}
//No blending for screen itself. Required for proper cursor rendering.
SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE);
screenTexture = SDL_CreateTexture(mainRenderer,
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING,
renderWidth, renderHeight);
if(nullptr == screenTexture)
{
logGlobal->error("Unable to create screen texture");
logGlobal->error(SDL_GetError());
throw std::runtime_error("Unable to create screen texture");
}
screen2 = CSDL_Ext::copySurface(screen);
if(nullptr == screen2)
{
throw std::runtime_error("Unable to copy surface\n");
}
screenBuf = bufOnScreen ? screen : screen2;
SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 0);
SDL_RenderClear(mainRenderer);
SDL_RenderPresent(mainRenderer);
if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool())
{
NotificationHandler::init(mainWindow);
}
return true;
}
//used only once during initialization
static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayIndex, bool resetVideo)
{
if(!recreateWindow(w, h, bpp, fullscreen, displayIndex))
{
throw std::runtime_error("Requested screen resolution is not available\n");
}
}
static void fullScreenChanged()
{
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
Settings full = settings.write["video"]["fullscreen"];
const bool toFullscreen = full->Bool();
auto bitsPerPixel = screen->format->BitsPerPixel;
auto w = screen->w;
auto h = screen->h;
if(!recreateWindow(w, h, bitsPerPixel, toFullscreen, -1))
{
//will return false and report error if video mode is not supported
return;
}
GH.totalRedraw();
}
static void handleEvent(SDL_Event & ev) static void handleEvent(SDL_Event & ev)
{ {
if((ev.type==SDL_QUIT) ||(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT))) if((ev.type==SDL_QUIT) ||(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT)))
@@ -1009,8 +555,11 @@ static void handleEvent(SDL_Event & ev)
CMM->menu->switchToTab("load"); CMM->menu->switchToTab("load");
break; break;
case EUserEvent::FULLSCREEN_TOGGLED: case EUserEvent::FULLSCREEN_TOGGLED:
fullScreenChanged(); {
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
GH.windowHandler().onFullscreenChanged();
break; break;
}
default: default:
logGlobal->error("Unknown user event. Code %d", ev.user.code); logGlobal->error("Unknown user event. Code %d", ev.user.code);
break; break;
@@ -1023,7 +572,10 @@ static void handleEvent(SDL_Event & ev)
switch (ev.window.event) { switch (ev.window.event) {
case SDL_WINDOWEVENT_RESTORED: case SDL_WINDOWEVENT_RESTORED:
#ifndef VCMI_IOS #ifndef VCMI_IOS
fullScreenChanged(); {
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
GH.windowHandler().onFullscreenChanged();
}
#endif #endif
break; break;
} }
@@ -1047,10 +599,8 @@ static void handleEvent(SDL_Event & ev)
boost::unique_lock<boost::mutex> lock(eventsM); boost::unique_lock<boost::mutex> lock(eventsM);
SDLEventsQueue.push(ev); SDLEventsQueue.push(ev);
} }
} }
static void mainLoop() static void mainLoop()
{ {
SettingsListener resChanged = settings.listen["video"]["fullscreen"]; SettingsListener resChanged = settings.listen["video"]["fullscreen"];
@@ -1071,7 +621,6 @@ static void mainLoop()
CSH->applyPacksOnLobbyScreen(); CSH->applyPacksOnLobbyScreen();
GH.renderFrame(); GH.renderFrame();
} }
} }
@@ -1108,29 +657,9 @@ static void quitApplication()
vstd::clear_pointer(console);// should be removed after everything else since used by logging vstd::clear_pointer(console);// should be removed after everything else since used by logging
boost::this_thread::sleep(boost::posix_time::milliseconds(750));//??? boost::this_thread::sleep(boost::posix_time::milliseconds(750));//???
if(!settings["session"]["headless"].Bool()) if(!settings["session"]["headless"].Bool())
{ GH.windowHandler().close();
if(settings["general"]["notifications"].Bool())
{
NotificationHandler::destroy();
}
cleanupRenderer();
if(nullptr != mainRenderer)
{
SDL_DestroyRenderer(mainRenderer);
mainRenderer = nullptr;
}
if(nullptr != mainWindow)
{
SDL_DestroyWindow(mainWindow);
mainWindow = nullptr;
}
SDL_Quit();
}
if(logConfig != nullptr) if(logConfig != nullptr)
{ {
@@ -1145,7 +674,6 @@ static void quitApplication()
void handleQuit(bool ask) void handleQuit(bool ask)
{ {
if(CSH->client && LOCPLINT && ask) if(CSH->client && LOCPLINT && ask)
{ {
CCS->curh->set(Cursor::Map::POINTER); CCS->curh->set(Cursor::Map::POINTER);

View File

@@ -75,6 +75,7 @@ set(client_SRCS
renderSDL/SDLImage.cpp renderSDL/SDLImage.cpp
renderSDL/SDLImageLoader.cpp renderSDL/SDLImageLoader.cpp
renderSDL/SDLRWwrapper.cpp renderSDL/SDLRWwrapper.cpp
renderSDL/WindowHandler.cpp
renderSDL/SDL_Extensions.cpp renderSDL/SDL_Extensions.cpp
widgets/Buttons.cpp widgets/Buttons.cpp
@@ -211,6 +212,7 @@ set(client_HEADERS
renderSDL/SDLImage.h renderSDL/SDLImage.h
renderSDL/SDLImageLoader.h renderSDL/SDLImageLoader.h
renderSDL/SDLRWwrapper.h renderSDL/SDLRWwrapper.h
renderSDL/WindowHandler.h
renderSDL/SDL_Extensions.h renderSDL/SDL_Extensions.h
renderSDL/SDL_PixelAccess.h renderSDL/SDL_PixelAccess.h

View File

@@ -123,7 +123,8 @@ public:
} }
}; };
extern std::string NAME; static const std::string NAME_AFFIX = "client";
static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
CServerHandler::CServerHandler() CServerHandler::CServerHandler()
: state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false) : state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false)

View File

@@ -18,6 +18,7 @@
#include "../CGameInfo.h" #include "../CGameInfo.h"
#include "../render/Colors.h" #include "../render/Colors.h"
#include "../renderSDL/SDL_Extensions.h" #include "../renderSDL/SDL_Extensions.h"
#include "../renderSDL/WindowHandler.h"
#include "../CMT.h" #include "../CMT.h"
#include "../CPlayerInterface.h" #include "../CPlayerInterface.h"
#include "../battle/BattleInterface.h" #include "../battle/BattleInterface.h"
@@ -95,9 +96,10 @@ void CGuiHandler::processLists(const ui16 activityFlag, std::function<void (std:
void CGuiHandler::init() void CGuiHandler::init()
{ {
windowHandlerInstance = std::make_unique<WindowHandler>();
shortcutsHandlerInstance = std::make_unique<ShortcutHandler>(); shortcutsHandlerInstance = std::make_unique<ShortcutHandler>();
mainFPSmng = new CFramerateManager(); mainFPSmng = new CFramerateManager(settings["video"]["targetfps"].Integer());
mainFPSmng->init(settings["video"]["targetfps"].Integer());
isPointerRelativeMode = settings["general"]["userRelativePointer"].Bool(); isPointerRelativeMode = settings["general"]["userRelativePointer"].Bool();
pointerSpeedMultiplier = settings["general"]["relativePointerSpeedMultiplier"].Float(); pointerSpeedMultiplier = settings["general"]["relativePointerSpeedMultiplier"].Float();
} }
@@ -803,7 +805,12 @@ void CGuiHandler::pushUserEvent(EUserEvent usercode, void * userdata)
SDL_PushEvent(&event); SDL_PushEvent(&event);
} }
CFramerateManager::CFramerateManager() WindowHandler & CGuiHandler::windowHandler()
{
return *windowHandlerInstance;
}
CFramerateManager::CFramerateManager(int newRate)
: rate(0) : rate(0)
, rateticks(0) , rateticks(0)
, fps(0) , fps(0)
@@ -811,7 +818,9 @@ CFramerateManager::CFramerateManager()
, accumulatedTime(0) , accumulatedTime(0)
, lastticks(0) , lastticks(0)
, timeElapsed(0) , timeElapsed(0)
{} {
init(newRate);
}
void CFramerateManager::init(int newRate) void CFramerateManager::init(int newRate)
{ {

View File

@@ -29,6 +29,7 @@ class CIntObject;
class IUpdateable; class IUpdateable;
class IShowActivatable; class IShowActivatable;
class IShowable; class IShowable;
class WindowHandler;
// TODO: event handling need refactoring // TODO: event handling need refactoring
enum class EUserEvent enum class EUserEvent
@@ -56,7 +57,7 @@ private:
ui32 accumulatedFrames; ui32 accumulatedFrames;
public: public:
CFramerateManager(); // initializes the manager with a given fps rate CFramerateManager(int newRate); // initializes the manager with a given fps rate
void init(int newRate); // needs to be called directly before the main game loop to reset the internal timer void init(int newRate); // needs to be called directly before the main game loop to reset the internal timer
void framerateDelay(); // needs to be called every game update cycle void framerateDelay(); // needs to be called every game update cycle
ui32 getElapsedMilliseconds() const {return this->timeElapsed;} ui32 getElapsedMilliseconds() const {return this->timeElapsed;}
@@ -95,6 +96,7 @@ private:
CIntObjectList doubleClickInterested; CIntObjectList doubleClickInterested;
CIntObjectList textInterested; CIntObjectList textInterested;
std::unique_ptr<WindowHandler> windowHandlerInstance;
void handleMouseButtonClick(CIntObjectList & interestedObjs, MouseButton btn, bool isPressed); void handleMouseButtonClick(CIntObjectList & interestedObjs, MouseButton btn, bool isPressed);
void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb); void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb);
@@ -135,6 +137,8 @@ public:
/// moves mouse pointer into specified position inside vcmi window /// moves mouse pointer into specified position inside vcmi window
void moveCursorToPosition(const Point & position); void moveCursorToPosition(const Point & position);
WindowHandler & windowHandler();
IUpdateable *curInt; IUpdateable *curInt;
Point lastClick; Point lastClick;

View File

@@ -31,6 +31,7 @@
#include "../../lib/NetPacksLobby.h" #include "../../lib/NetPacksLobby.h"
#include "../../lib/CGeneralTextHandler.h" #include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CModHandler.h" #include "../../lib/CModHandler.h"
#include "../../lib/GameSettings.h" #include "../../lib/GameSettings.h"
#include "../../lib/filesystem/Filesystem.h" #include "../../lib/filesystem/Filesystem.h"

View File

@@ -0,0 +1,407 @@
/*
* WindowHandler.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "WindowHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/NotificationHandler.h"
#include "CMT.h"
#include "SDL_Extensions.h"
#include <SDL.h>
static const std::string NAME_AFFIX = "client";
static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
Point WindowHandler::getPreferredLogicalResolution() const
{
// TODO: CONFIGURABLE ADVMAP - IMPLEMENT UI SCALE SETTING
return {1280, 720};
}
Point WindowHandler::getPreferredRenderingResolution() const
{
if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_WINDOWED)
{
SDL_Rect bounds;
SDL_GetDisplayBounds(getPreferredDisplayIndex(), &bounds);
return Point(bounds.w, bounds.h);
}
else
{
const JsonNode & video = settings["video"];
int width = video["screenRes"]["width"].Integer();
int height = video["screenRes"]["height"].Integer();
return Point(width, height);
}
}
int WindowHandler::getPreferredDisplayIndex() const
{
#ifdef VCMI_MOBILE
// Assuming no multiple screens on Android / ios?
return 0;
#else
if (mainWindow != nullptr)
return SDL_GetWindowDisplayIndex(mainWindow);
else
return settings["video"]["displayIndex"].Integer();
#endif
}
EWindowMode WindowHandler::getPreferredWindowMode() const
{
#ifdef VCMI_MOBILE
// On Android / ios game will always render to screen size
return EWindowMode::FULLSCREEN_WINDOWED;
#else
const JsonNode & video = settings["video"];
bool fullscreen = video["fullscreen"].Bool();
bool realFullscreen = settings["video"]["realFullscreen"].Bool();
if (!fullscreen)
return EWindowMode::WINDOWED;
if (realFullscreen)
return EWindowMode::FULLSCREEN_TRUE;
else
return EWindowMode::FULLSCREEN_WINDOWED;
#endif
}
WindowHandler::WindowHandler()
{
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE))
{
logGlobal->error("Something was wrong: %s", SDL_GetError());
exit(-1);
}
const auto & logCallback = [](void * userdata, int category, SDL_LogPriority priority, const char * message)
{
logGlobal->debug("SDL(category %d; priority %d) %s", category, priority, message);
};
SDL_LogSetOutputFunction(logCallback, nullptr);
#ifdef VCMI_ANDROID
// manually setting egl pixel format, as a possible solution for sdl2<->android problem
// https://bugzilla.libsdl.org/show_bug.cgi?id=2291
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
#endif // VCMI_ANDROID
validateSettings();
recreateWindow();
}
bool WindowHandler::recreateWindow()
{
destroyScreen();
if(mainWindow == nullptr)
initializeRenderer();
else
updateFullscreenState();
initializeScreen();
if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool())
{
NotificationHandler::init(mainWindow);
}
return true;
}
void WindowHandler::updateFullscreenState()
{
#if !defined(VCMI_MOBILE)
int displayIndex = getPreferredDisplayIndex();
switch(getPreferredWindowMode())
{
case EWindowMode::FULLSCREEN_TRUE:
{
SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN);
SDL_DisplayMode mode;
SDL_GetDesktopDisplayMode(displayIndex, &mode);
Point resolution = getPreferredRenderingResolution();
mode.w = resolution.x;
mode.h = resolution.y;
SDL_SetWindowDisplayMode(mainWindow, &mode);
SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex));
return;
}
case EWindowMode::FULLSCREEN_WINDOWED:
{
SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex));
return;
}
case EWindowMode::WINDOWED:
{
Point resolution = getPreferredLogicalResolution();
SDL_SetWindowFullscreen(mainWindow, 0);
SDL_SetWindowSize(mainWindow, resolution.x, resolution.y);
SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex));
return;
}
}
#endif
}
void WindowHandler::initializeRenderer()
{
mainWindow = createWindow();
if(mainWindow == nullptr)
throw std::runtime_error("Unable to create window\n");
//create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible
mainRenderer = SDL_CreateRenderer(mainWindow, getPreferredRenderingDriver(), 0);
if(mainRenderer == nullptr)
throw std::runtime_error("Unable to create renderer\n");
SDL_RendererInfo info;
SDL_GetRendererInfo(mainRenderer, &info);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
logGlobal->info("Created renderer %s", info.name);
}
void WindowHandler::initializeScreen()
{
#ifdef VCMI_ENDIAN_BIG
int bmask = 0xff000000;
int gmask = 0x00ff0000;
int rmask = 0x0000ff00;
int amask = 0x000000ff;
#else
int bmask = 0x000000ff;
int gmask = 0x0000ff00;
int rmask = 0x00ff0000;
int amask = 0xFF000000;
#endif
auto logicalSize = getPreferredLogicalResolution();
SDL_RenderSetLogicalSize(mainRenderer, logicalSize.x, logicalSize.y);
screen = SDL_CreateRGBSurface(0, logicalSize.x, logicalSize.y, 32, rmask, gmask, bmask, amask);
if(nullptr == screen)
{
logGlobal->error("Unable to create surface %dx%d with %d bpp: %s", logicalSize.x, logicalSize.y, 32, SDL_GetError());
throw std::runtime_error("Unable to create surface");
}
//No blending for screen itself. Required for proper cursor rendering.
SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE);
screenTexture = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, logicalSize.x, logicalSize.y);
if(nullptr == screenTexture)
{
logGlobal->error("Unable to create screen texture");
logGlobal->error(SDL_GetError());
throw std::runtime_error("Unable to create screen texture");
}
screen2 = CSDL_Ext::copySurface(screen);
if(nullptr == screen2)
{
throw std::runtime_error("Unable to copy surface\n");
}
screenBuf = screen;
SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 0);
SDL_RenderClear(mainRenderer);
SDL_RenderPresent(mainRenderer);
}
SDL_Window * WindowHandler::createWindowImpl(Point dimensions, int displayIndex, int flags, bool center)
{
int positionFlags = center ? SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex) : SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex);
return SDL_CreateWindow(NAME.c_str(), positionFlags, positionFlags, dimensions.x, dimensions.y, flags);
}
SDL_Window * WindowHandler::createWindow()
{
#ifndef VCMI_MOBILE
const JsonNode & video = settings["video"];
int displayIndex = video["displayIndex"].Float();
bool fullscreen = video["fullscreen"].Bool();
bool realFullscreen = video["realFullscreen"].Bool();
Point dimensions = getPreferredRenderingResolution();
if(!fullscreen)
return createWindowImpl(dimensions, displayIndex, 0, true);
if(realFullscreen)
return createWindowImpl(dimensions, displayIndex, SDL_WINDOW_FULLSCREEN, false);
else
return createWindowImpl(Point(), displayIndex, SDL_WINDOW_FULLSCREEN_DESKTOP, false);
#endif
#ifdef VCMI_IOS
SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "1");
SDL_SetHint(SDL_HINT_RETURN_KEY_HIDES_IME, "1");
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
uint32_t windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI;
SDL_Window * result = createWindowImpl(Point(), displayIndex, windowFlags | SDL_WINDOW_METAL, false);
if(result != nullptr)
return result;
logGlobal->warn("Metal unavailable, using OpenGLES");
return createWindowImpl(Point(), displayIndex, windowFlags, false);
#endif
#ifdef VCMI_ANDROID
return createWindowImpl(Point(), displayIndex, SDL_WINDOW_FULLSCREEN, false);
#endif
}
void WindowHandler::onFullscreenChanged()
{
if(!recreateWindow())
{
//will return false and report error if video mode is not supported
return;
}
GH.totalRedraw();
}
void WindowHandler::validateSettings()
{
#if !defined(VCMI_MOBILE)
{
int displayIndex = settings["video"]["displayIndex"].Integer();
int displaysCount = SDL_GetNumVideoDisplays();
if (displayIndex >= displaysCount)
{
Settings writer = settings.write["video"]["displayIndex"];
writer->Float() = 0;
}
}
if (getPreferredWindowMode() == EWindowMode::WINDOWED)
{
//we only check that our desired window size fits on screen
int displayIndex = getPreferredDisplayIndex();
Point resolution = getPreferredRenderingResolution();
SDL_DisplayMode mode;
if (SDL_GetDesktopDisplayMode(displayIndex, &mode) == 0)
{
if(resolution.x > mode.w || resolution.y > mode.h)
{
Settings writer = settings.write["video"]["screenRes"];
writer["width"].Float() = mode.w;
writer["height"].Float() = mode.h;
}
}
}
if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_TRUE)
{
// TODO: check that display supports selected resolution
}
#endif
}
int WindowHandler::getPreferredRenderingDriver() const
{
int result = -1;
const JsonNode & video = settings["video"];
int driversCount = SDL_GetNumRenderDrivers();
std::string preferredDriverName = video["driver"].String();
logGlobal->info("Found %d render drivers", driversCount);
for(int it = 0; it < driversCount; it++)
{
SDL_RendererInfo info;
SDL_GetRenderDriverInfo(it, &info);
std::string driverName(info.name);
if(!preferredDriverName.empty() && driverName == preferredDriverName)
{
result = it;
logGlobal->info("\t%s (active)", driverName);
}
else
logGlobal->info("\t%s", driverName);
}
return result;
}
void WindowHandler::destroyScreen()
{
screenBuf = nullptr; //it`s a link - just nullify
if(nullptr != screen2)
{
SDL_FreeSurface(screen2);
screen2 = nullptr;
}
if(nullptr != screen)
{
SDL_FreeSurface(screen);
screen = nullptr;
}
if(nullptr != screenTexture)
{
SDL_DestroyTexture(screenTexture);
screenTexture = nullptr;
}
}
void WindowHandler::destroyWindow()
{
if(nullptr != mainRenderer)
{
SDL_DestroyRenderer(mainRenderer);
mainRenderer = nullptr;
}
if(nullptr != mainWindow)
{
SDL_DestroyWindow(mainWindow);
mainWindow = nullptr;
}
}
void WindowHandler::close()
{
if(settings["general"]["notifications"].Bool())
NotificationHandler::destroy();
destroyScreen();
destroyWindow();
SDL_Quit();
}

View File

@@ -0,0 +1,74 @@
/*
* WindowHandler.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
struct SDL_Texture;
struct SDL_Window;
struct SDL_Renderer;
struct SDL_Surface;
#include "../../lib/Point.h"
enum class EWindowMode
{
// game runs in a window that covers part of the screen
WINDOWED,
// game runs in a 'window' that always covers entire screen and uses unmodified desktop resolution
// The only mode that is available on mobile devices
FULLSCREEN_WINDOWED,
// game runs in a fullscreen mode with resolution selected by player
FULLSCREEN_TRUE
};
/// This class is responsible for management of game window and its main rendering surface
class WindowHandler
{
/// Dimensions of target surfaces/textures, this value is what game logic views as screen size
Point getPreferredLogicalResolution() const;
/// Dimensions of output window, if different from logicalResolution SDL will perform scaling
/// This value is what player views as window size
Point getPreferredRenderingResolution() const;
EWindowMode getPreferredWindowMode() const;
/// Returns index of display on which window should be created
int getPreferredDisplayIndex() const;
/// Returns index of rendering driver preferred by player or -1 if no preference
int getPreferredRenderingDriver() const;
/// Creates SDL window with specified parameters
SDL_Window * createWindowImpl(Point dimensions, int displayIndex, int flags, bool center);
/// Creates SDL window using OS-specific settings & user-specific config
SDL_Window * createWindow();
void initializeRenderer();
void initializeScreen();
void updateFullscreenState();
bool recreateWindow();
void destroyScreen();
void destroyWindow();
void validateSettings();
public:
/// Creates and initializes screen, window and SDL state
WindowHandler();
/// Updates and potentially recreates target screen to match selected fullscreen status
void onFullscreenChanged();
/// De-initializes and destroys screen, window and SDL state
void close();
};

View File

@@ -12,7 +12,6 @@
#include "CWindowObject.h" #include "CWindowObject.h"
#include "../lib/GameConstants.h" #include "../lib/GameConstants.h"
#include "../lib/ResourceSet.h" #include "../lib/ResourceSet.h"
#include "../lib/CConfigHandler.h"
#include "../lib/int3.h" #include "../lib/int3.h"
#include "../widgets/CWindowWithArtifacts.h" #include "../widgets/CWindowWithArtifacts.h"
#include "../widgets/CGarrisonInt.h" #include "../widgets/CGarrisonInt.h"