diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index 8974b55cb0..c4f9f23af9 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -105,8 +105,9 @@ showsky = true ; for a debugging of a system without GL_KHR_debug. gl.checkerrorafterswap = false -; Disable hardware cursors -nohwcursor = false +; Different ways to draw a cursor, possible values are "sdl" and "system". +; The "system" one doesn't support a visual change of the cursor. +cursorbackend = "sdl" ; Specify the render path. This can be one of: ; default Automatically select one of the below, depending on system capabilities diff --git a/source/gui/GUIManager.cpp b/source/gui/GUIManager.cpp index bcdad45832..edff0fb7e0 100644 --- a/source/gui/GUIManager.cpp +++ b/source/gui/GUIManager.cpp @@ -25,6 +25,7 @@ #include "ps/Filesystem.h" #include "ps/GameSetup/Config.h" #include "ps/Profile.h" +#include "ps/VideoMode.h" #include "ps/XML/Xeromyces.h" #include "scriptinterface/FunctionWrapper.h" #include "scriptinterface/ScriptContext.h" @@ -146,7 +147,7 @@ void CGUIManager::SGUIPage::LoadPage(std::shared_ptr scriptContex hotloadData = Script::WriteStructuredClone(rq, hotloadDataVal); } - g_CursorName = g_DefaultCursor; + g_VideoMode.ResetCursor(); inputs.clear(); gui.reset(new CGUI(scriptContext)); diff --git a/source/gui/Scripting/JSInterface_GUIManager.cpp b/source/gui/Scripting/JSInterface_GUIManager.cpp index c448da9d01..c66b3dfedf 100644 --- a/source/gui/Scripting/JSInterface_GUIManager.cpp +++ b/source/gui/Scripting/JSInterface_GUIManager.cpp @@ -23,6 +23,7 @@ #include "gui/GUIManager.h" #include "gui/ObjectBases/IGUIObject.h" #include "ps/GameSetup/Config.h" +#include "ps/VideoMode.h" #include "scriptinterface/FunctionWrapper.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/StructuredClone.h" @@ -52,16 +53,14 @@ void PopGuiPage(const ScriptRequest& rq, JS::HandleValue args) g_GUI->PopPage(Script::WriteStructuredClone(rq, args)); } -std::wstring SetCursor(const std::wstring& name) +void SetCursor(const std::wstring& name) { - std::wstring old = g_CursorName; - g_CursorName = name; - return old; + g_VideoMode.SetCursor(name); } void ResetCursor() { - g_CursorName = g_DefaultCursor; + g_VideoMode.ResetCursor(); } bool TemplateExists(const std::string& templateName) diff --git a/source/lib/res/graphics/cursor.cpp b/source/lib/res/graphics/cursor.cpp deleted file mode 100644 index 746383231c..0000000000 --- a/source/lib/res/graphics/cursor.cpp +++ /dev/null @@ -1,356 +0,0 @@ -/* Copyright (C) 2020 Wildfire Games. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * mouse cursors (either via OpenGL texture or hardware) - */ - -#include "precompiled.h" -#include "cursor.h" - -#include -#include -#include - -#include "lib/external_libraries/libsdl.h" -#include "lib/ogl.h" -#include "lib/res/h_mgr.h" -#include "ogl_tex.h" - -class SDLCursor -{ - SDL_Surface* surface; - SDL_Cursor* cursor; - -public: - Status create(const PIVFS& vfs, const VfsPath& pathname, int hotspotx_, int hotspoty_, double scale) - { - std::shared_ptr file; size_t fileSize; - RETURN_STATUS_IF_ERR(vfs->LoadFile(pathname, file, fileSize)); - - Tex t; - RETURN_STATUS_IF_ERR(t.decode(file, fileSize)); - - // convert to required BGRA format. - const size_t flags = (t.m_Flags | TEX_BGR) & ~TEX_DXT; - RETURN_STATUS_IF_ERR(t.transform_to(flags)); - void* bgra_img = t.get_data(); - if(!bgra_img) - WARN_RETURN(ERR::FAIL); - - surface = SDL_CreateRGBSurfaceFrom(bgra_img, (int)t.m_Width, (int)t.m_Height, 32, (int)t.m_Width*4, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); - if(!surface) - return ERR::FAIL; - if(scale != 1.0) - { - SDL_Surface* scaled_surface = SDL_CreateRGBSurface(0, surface->w * scale, surface->h * scale, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); - if(!scaled_surface) - return ERR::FAIL; - if(SDL_BlitScaled(surface, NULL, scaled_surface, NULL)) - return ERR::FAIL; - SDL_FreeSurface(surface); - surface = scaled_surface; - } - cursor = SDL_CreateColorCursor(surface, hotspotx_, hotspoty_); - if(!cursor) - return ERR::FAIL; - - return INFO::OK; - } - - void set() - { - SDL_SetCursor(cursor); - } - - void destroy() - { - SDL_FreeCursor(cursor); - SDL_FreeSurface(surface); - } -}; - -// no init is necessary because this is stored in struct Cursor, which -// is 0-initialized by h_mgr. -class GLCursor -{ - Handle ht; - - GLint w, h; - int hotspotx, hotspoty; - -public: - Status create(const PIVFS& vfs, const VfsPath& pathname, int hotspotx_, int hotspoty_, double scale) - { - ht = ogl_tex_load(vfs, pathname); - RETURN_STATUS_IF_ERR(ht); - - size_t width, height; - (void)ogl_tex_get_size(ht, &width, &height, 0); - w = (GLint)(width * scale); - h = (GLint)(height * scale); - - hotspotx = hotspotx_; hotspoty = hotspoty_; - - (void)ogl_tex_set_filter(ht, GL_NEAREST); - (void)ogl_tex_upload(ht); - return INFO::OK; - } - - void destroy() - { - // note: we're stored in a resource => ht is initially 0 => - // this is safe, no need for an is_valid flag - (void)ogl_tex_free(ht); - } - - void draw(int x, int y) const - { -#if CONFIG2_GLES - UNUSED2(x); UNUSED2(y); -#warning TODO: implement cursors for GLES -#else - (void)ogl_tex_bind(ht); - glEnable(GL_TEXTURE_2D); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - glDisable(GL_DEPTH_TEST); - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - - glBegin(GL_QUADS); - glTexCoord2i(1, 0); glVertex2i( x-hotspotx+w, y+hotspoty ); - glTexCoord2i(0, 0); glVertex2i( x-hotspotx, y+hotspoty ); - glTexCoord2i(0, 1); glVertex2i( x-hotspotx, y+hotspoty-h ); - glTexCoord2i(1, 1); glVertex2i( x-hotspotx+w, y+hotspoty-h ); - glEnd(); - - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); -#endif - } - - Status validate() const - { - const GLint A = 128; // no cursor is expected to get this big - if(w > A || h > A || hotspotx > A || hotspoty > A) - WARN_RETURN(ERR::_1); - if(ht < 0) - WARN_RETURN(ERR::_2); - return INFO::OK; - } -}; - -enum CursorKind -{ - CK_Default, - CK_SDL, - CK_OpenGL -}; - -struct Cursor -{ - double scale; - - // require kind == CK_OpenGL after reload - bool forceGL; - - CursorKind kind; - - // valid iff kind == CK_SDL - SDLCursor sdl_cursor; - - // valid iff kind == CK_OpenGL - GLCursor gl_cursor; -}; - -H_TYPE_DEFINE(Cursor); - -static void Cursor_init(Cursor* c, va_list args) -{ - c->scale = va_arg(args, double); - c->forceGL = (va_arg(args, int) != 0); -} - -static void Cursor_dtor(Cursor* c) -{ - switch(c->kind) - { - case CK_Default: - break; // nothing to do - - case CK_SDL: - c->sdl_cursor.destroy(); - break; - - case CK_OpenGL: - c->gl_cursor.destroy(); - break; - - default: - DEBUG_WARN_ERR(ERR::LOGIC); - break; - } -} - -static Status Cursor_reload(Cursor* c, const PIVFS& vfs, const VfsPath& name, Handle) -{ - const VfsPath pathname(VfsPath(L"art/textures/cursors") / name); - - // read pixel offset of the cursor's hotspot [the bit of it that's - // drawn at (g_mouse_x,g_mouse_y)] from file. - int hotspotx = 0, hotspoty = 0; - { - const VfsPath pathnameHotspot = pathname.ChangeExtension(L".txt"); - std::shared_ptr buf; size_t size; - RETURN_STATUS_IF_ERR(vfs->LoadFile(pathnameHotspot, buf, size)); - std::wstringstream s(std::wstring((const wchar_t*)buf.get(), size)); - s >> hotspotx >> hotspoty; - } - - const VfsPath pathnameImage = pathname.ChangeExtension(L".png"); - - // try loading as SDL2 cursor - if(!c->forceGL && c->sdl_cursor.create(vfs, pathnameImage, hotspotx, hotspoty, c->scale) == INFO::OK) - c->kind = CK_SDL; - // fall back to GLCursor (system cursor code is disabled or failed) - else if(c->gl_cursor.create(vfs, pathnameImage, hotspotx, hotspoty, c->scale) == INFO::OK) - c->kind = CK_OpenGL; - // everything failed, leave cursor unchanged - else - c->kind = CK_Default; - - return INFO::OK; -} - -static Status Cursor_validate(const Cursor* c) -{ - switch(c->kind) - { - case CK_Default: - break; // nothing to do - - case CK_SDL: - break; // nothing to do - - case CK_OpenGL: - RETURN_STATUS_IF_ERR(c->gl_cursor.validate()); - break; - - default: - WARN_RETURN(ERR::_2); - break; - } - - return INFO::OK; -} - -static Status Cursor_to_string(const Cursor* c, wchar_t* buf) -{ - const wchar_t* type; - switch(c->kind) - { - case CK_Default: - type = L"default"; - break; - - case CK_SDL: - type = L"sdl"; - break; - - case CK_OpenGL: - type = L"gl"; - break; - - default: - DEBUG_WARN_ERR(ERR::LOGIC); - type = L"?"; - break; - } - - swprintf_s(buf, H_STRING_LEN, L"cursor (%ls)", type); - return INFO::OK; -} - - -// note: these standard resource interface functions are not exposed to the -// caller. all we need here is storage for the SDL_Cursor / GLCursor and -// a name -> data lookup mechanism, both provided by h_mgr. -// in other words, we continually create/free the cursor resource in -// cursor_draw and trust h_mgr's caching to absorb it. - -static Handle cursor_load(const PIVFS& vfs, const VfsPath& name, double scale, bool forceGL) -{ - return h_alloc(H_Cursor, vfs, name, 0, scale, (int)forceGL); -} - -void cursor_shutdown() -{ - h_mgr_free_type(H_Cursor); -} - -static Status cursor_free(Handle& h) -{ - return h_free(h, H_Cursor); -} - - -Status cursor_draw(const PIVFS& vfs, const wchar_t* name, int x, int y, double scale, bool forceGL) -{ - // hide the cursor - if(!name) - { - SDL_ShowCursor(SDL_DISABLE); - return INFO::OK; - } - - Handle hc = cursor_load(vfs, name, scale, forceGL); - // TODO: if forceGL changes at runtime after a cursor is first created, - // we might reuse a cached version of the cursor with the old forceGL flag - - RETURN_STATUS_IF_ERR(hc); // silently ignore failures - - H_DEREF(hc, Cursor, c); - - switch(c->kind) - { - case CK_Default: - break; - - case CK_SDL: - c->sdl_cursor.set(); - SDL_ShowCursor(SDL_ENABLE); - break; - - case CK_OpenGL: - c->gl_cursor.draw(x, y); - SDL_ShowCursor(SDL_DISABLE); - break; - - default: - DEBUG_WARN_ERR(ERR::LOGIC); - break; - } - - (void)cursor_free(hc); - return INFO::OK; -} diff --git a/source/lib/res/graphics/cursor.h b/source/lib/res/graphics/cursor.h deleted file mode 100644 index 5d1d81fd1a..0000000000 --- a/source/lib/res/graphics/cursor.h +++ /dev/null @@ -1,57 +0,0 @@ -/* Copyright (C) 2017 Wildfire Games. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * mouse cursors (either via OpenGL texture or hardware) - */ - -#ifndef INCLUDED_GRAPHICS_CURSOR -#define INCLUDED_GRAPHICS_CURSOR - -#include "lib/file/vfs/vfs.h" - -/** - * Draw the cursor on-screen. - * - * @param vfs - * @param name Base name of cursor or zero to hide the cursor. - * @param x,y Coordinates [pixels] (origin at lower left) - * (the origin is convenient for drawing via OpenGL, but requires the - * mouse Y coordinate to be subtracted from the client area height. - * Making the caller responsible for this avoids a dependency on - * the g_yres global variable.) - * @param scale Scale factor for drawing size the cursor. - * @param forceGL Require the OpenGL cursor implementation, not hardware cursor - * - * Uses a hardware mouse cursor where available, otherwise a - * portable OpenGL implementation. - **/ -extern Status cursor_draw(const PIVFS& vfs, const wchar_t* name, int x, int y, double scale, bool forceGL); - -/** - * Forcibly frees all cursor handles. - * - * Currently used just prior to SDL shutdown. - */ -void cursor_shutdown(); - -#endif // #ifndef INCLUDED_GRAPHICS_CURSOR diff --git a/source/main.cpp b/source/main.cpp index c5ecd824c9..780eb37f95 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -159,12 +159,6 @@ static InReaction MainInputHandler(const SDL_Event_* ev) case SDL_WINDOWEVENT: switch(ev->ev.window.event) { - case SDL_WINDOWEVENT_ENTER: - RenderCursor(true); - break; - case SDL_WINDOWEVENT_LEAVE: - RenderCursor(false); - break; case SDL_WINDOWEVENT_RESIZED: g_ResizedW = ev->ev.window.data1; g_ResizedH = ev->ev.window.data2; diff --git a/source/ps/GameSetup/Config.cpp b/source/ps/GameSetup/Config.cpp index f2bc15562a..f207ccccd3 100644 --- a/source/ps/GameSetup/Config.cpp +++ b/source/ps/GameSetup/Config.cpp @@ -26,11 +26,6 @@ #include "ps/GameSetup/CmdLineArgs.h" // (these variables are documented in the header.) - -const wchar_t g_DefaultCursor[] = L"default-arrow"; - -CStrW g_CursorName = g_DefaultCursor; - bool g_PauseOnFocusLoss = false; int g_xres, g_yres; diff --git a/source/ps/GameSetup/Config.h b/source/ps/GameSetup/Config.h index 95fc997e21..d3a758fa0a 100644 --- a/source/ps/GameSetup/Config.h +++ b/source/ps/GameSetup/Config.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -29,9 +29,6 @@ extern float g_GuiScale; extern bool g_Quickstart; extern bool g_DisableAudio; -extern CStrW g_CursorName; -extern const wchar_t g_DefaultCursor[]; - class CmdLineArgs; extern void CONFIG_Init(const CmdLineArgs& args); diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index 48cca3b2d9..3a9b1c3cf6 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -25,7 +25,6 @@ #include "lib/external_libraries/libsdl.h" #include "lib/file/common/file_stats.h" #include "lib/res/h_mgr.h" -#include "lib/res/graphics/cursor.h" #include "graphics/CinemaManager.h" #include "graphics/Color.h" @@ -113,7 +112,6 @@ ERROR_TYPE(System, RequiredExtensionsMissing); bool g_DoRenderGui = true; bool g_DoRenderLogger = true; -bool g_DoRenderCursor = true; thread_local std::shared_ptr g_ScriptContext; @@ -284,54 +282,6 @@ void Render() g_ProfileViewer.RenderProfile(); ogl_WarnIfError(); - // Draw the cursor (or set the Windows cursor, on Windows) - if (g_DoRenderCursor) - { - PROFILE3_GPU("cursor"); - CStrW cursorName = g_CursorName; - if (cursorName.empty()) - { - cursor_draw(g_VFS, NULL, g_mouse_x, g_yres-g_mouse_y, g_GuiScale, false); - } - else - { - bool forceGL = false; - CFG_GET_VAL("nohwcursor", forceGL); - -#if CONFIG2_GLES -#warning TODO: implement cursors for GLES -#else - // set up transform for GL cursor - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glLoadIdentity(); - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glLoadIdentity(); - CMatrix3D transform; - transform.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); - glLoadMatrixf(&transform._11); -#endif - -#if OS_ANDROID -#warning TODO: cursors for Android -#else - if (cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y, g_GuiScale, forceGL) < 0) - LOGWARNING("Failed to draw cursor '%s'", utf8_from_wstring(cursorName)); -#endif - -#if CONFIG2_GLES -#warning TODO: implement cursors for GLES -#else - // restore transform - glMatrixMode(GL_PROJECTION); - glPopMatrix(); - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); -#endif - } - } - glEnable(GL_DEPTH_TEST); g_Renderer.EndFrame(); @@ -539,9 +489,6 @@ static void ShutdownPs() SAFE_DELETE(g_GUI); UnloadHotkeys(); - - // disable the special Windows cursor, or free textures for OGL cursors - cursor_draw(g_VFS, 0, g_mouse_x, g_yres-g_mouse_y, 1.0, false); } @@ -661,9 +608,6 @@ void Shutdown(int flags) g_Profiler2.ShutdownGPU(); - // Free cursors before shutting down SDL, as they may depend on SDL. - cursor_shutdown(); - TIMER_BEGIN(L"shutdown SDL"); ShutdownSDL(); TIMER_END(L"shutdown SDL"); @@ -1087,11 +1031,6 @@ void RenderLogger(bool RenderingState) g_DoRenderLogger = RenderingState; } -void RenderCursor(bool RenderingState) -{ - g_DoRenderCursor = RenderingState; -} - /** * Temporarily loads a scenario map and retrieves the "ScriptSettings" JSON * data from it. diff --git a/source/ps/GameSetup/GameSetup.h b/source/ps/GameSetup/GameSetup.h index 87bf6398b8..03d3ae1a24 100644 --- a/source/ps/GameSetup/GameSetup.h +++ b/source/ps/GameSetup/GameSetup.h @@ -74,12 +74,6 @@ enum ShutdownFlags extern void RenderGui(bool RenderingState); extern void RenderLogger(bool RenderingState); -/** - * enable/disable rendering of the cursor - this does not hide cursor, but reverts to OS style - */ -extern void RenderCursor(bool RenderingState); - - class CmdLineArgs; class Paths; extern const std::vector& GetMods(const CmdLineArgs& args, int flags); diff --git a/source/ps/Util.cpp b/source/ps/Util.cpp index 8439e395b6..dbf64a4290 100644 --- a/source/ps/Util.cpp +++ b/source/ps/Util.cpp @@ -19,34 +19,32 @@ #include "ps/Util.h" -#include "lib/posix/posix_utsname.h" -#include "lib/ogl.h" -#include "lib/timer.h" -#include "lib/bits.h" // round_up +#include "graphics/GameView.h" +#include "i18n/L10n.h" #include "lib/allocators/shared_ptr.h" -#include "lib/sysdep/sysdep.h" // sys_OpenFile -#include "lib/sysdep/gfx.h" -#include "lib/sysdep/cpu.h" -#include "lib/sysdep/os_cpu.h" +#include "lib/bits.h" // round_up +#include "lib/ogl.h" +#include "lib/posix/posix_utsname.h" #if ARCH_X86_X64 #include "lib/sysdep/arch/x86_x64/topology.h" #endif +#include "lib/sysdep/gfx.h" +#include "lib/sysdep/cpu.h" +#include "lib/sysdep/os_cpu.h" #include "lib/sysdep/smbios.h" +#include "lib/sysdep/sysdep.h" // sys_OpenFile #include "lib/tex/tex.h" - -#include "i18n/L10n.h" +#include "lib/timer.h" #include "lib/utf8.h" - -#include "ps/GameSetup/Config.h" -#include "ps/GameSetup/GameSetup.h" -#include "ps/Game.h" +#include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" +#include "ps/Game.h" +#include "ps/GameSetup/Config.h" +#include "ps/GameSetup/GameSetup.h" #include "ps/Pyrogenesis.h" #include "ps/VideoMode.h" #include "renderer/Renderer.h" -#include "maths/MathUtil.h" -#include "graphics/GameView.h" #if CONFIG2_AUDIO #include "soundmanager/SoundManager.h" @@ -55,8 +53,6 @@ #include #include -extern CStrW g_CursorName; - static std::string SplitExts(const char *exts) { std::string str = exts; @@ -376,10 +372,6 @@ void WriteBigScreenshot(const VfsPath& extension, int tiles, int tileWidth, int glReadBuffer(GL_FRONT); #endif - // Hide the cursor - CStrW oldCursor = g_CursorName; - g_CursorName = L""; - // Render each tile CMatrix3D projection; projection.SetIdentity(); @@ -412,9 +404,6 @@ void WriteBigScreenshot(const VfsPath& extension, int tiles, int tileWidth, int } } - // Restore the old cursor - g_CursorName = oldCursor; - #if !CONFIG2_GLES // Restore the buffer settings glDrawBuffer(oldDrawBuffer); diff --git a/source/ps/VideoMode.cpp b/source/ps/VideoMode.cpp index 7642cde319..19b59e726a 100644 --- a/source/ps/VideoMode.cpp +++ b/source/ps/VideoMode.cpp @@ -45,6 +45,8 @@ int DEFAULT_WINDOW_H = 768; int DEFAULT_FULLSCREEN_W = 1024; int DEFAULT_FULLSCREEN_H = 768; +const wchar_t DEFAULT_CURSOR_NAME[] = L"default-arrow"; + } // anonymous namespace #if OS_WIN @@ -59,11 +61,162 @@ extern void wutil_EnableHiDPIOnWindows(); CVideoMode g_VideoMode; +class CVideoMode::CCursor +{ +public: + enum class CursorBackend + { + SDL, + SYSTEM + }; + + CCursor(); + ~CCursor(); + + void SetCursor(const CStrW& name); + void ResetCursor(); + +private: + CursorBackend m_CursorBackend = CursorBackend::SYSTEM; + SDL_Surface* m_CursorSurface = nullptr; + SDL_Cursor* m_Cursor = nullptr; + CStrW m_CursorName; +}; + +CVideoMode::CCursor::CCursor() +{ + std::string cursorBackend; + CFG_GET_VAL("cursorbackend", cursorBackend); + if (cursorBackend == "sdl") + m_CursorBackend = CursorBackend::SDL; + else + m_CursorBackend = CursorBackend::SYSTEM; + + ResetCursor(); +} + +CVideoMode::CCursor::~CCursor() +{ + if (m_Cursor) + SDL_FreeCursor(m_Cursor); + if (m_CursorSurface) + SDL_FreeSurface(m_CursorSurface); +} + +void CVideoMode::CCursor::SetCursor(const CStrW& name) +{ + if (m_CursorBackend == CursorBackend::SYSTEM || m_CursorName == name) + return; + m_CursorName = name; + + if (m_Cursor) + SDL_FreeCursor(m_Cursor); + if (m_CursorSurface) + SDL_FreeSurface(m_CursorSurface); + + if (name.empty()) + { + SDL_ShowCursor(SDL_DISABLE); + return; + } + + const VfsPath pathBaseName(VfsPath(L"art/textures/cursors") / name); + + // Read pixel offset of the cursor's hotspot [the bit of it that's + // drawn at (g_mouse_x,g_mouse_y)] from file. + int hotspotX = 0, hotspotY = 0; + { + const VfsPath pathHotspotName = pathBaseName.ChangeExtension(L".txt"); + std::shared_ptr buffer; + size_t size; + if (g_VFS->LoadFile(pathHotspotName, buffer, size) != INFO::OK) + { + LOGERROR("Can't load hotspot for cursor: %s", pathHotspotName.string8().c_str()); + return; + } + std::wstringstream s(std::wstring(reinterpret_cast(buffer.get()), size)); + s >> hotspotX >> hotspotY; + } + + const VfsPath pathImageName = pathBaseName.ChangeExtension(L".png"); + + std::shared_ptr file; + size_t fileSize; + if (g_VFS->LoadFile(pathImageName, file, fileSize) != INFO::OK) + { + LOGERROR("Can't load image for cursor: %s", pathImageName.string8().c_str()); + return; + } + + Tex t; + if (t.decode(file, fileSize) != INFO::OK) + { + LOGERROR("Can't decode image for cursor"); + return; + } + + // Convert to required BGRA format. + const size_t flags = (t.m_Flags | TEX_BGR) & ~TEX_DXT; + if (t.transform_to(flags) != INFO::OK) + { + LOGERROR("Can't transform image for cursor"); + return; + } + void* imageBGRA = t.get_data(); + if (!imageBGRA) + { + LOGERROR("Transformed image is empty for cursor"); + return; + } + + m_CursorSurface = SDL_CreateRGBSurfaceFrom(imageBGRA, + static_cast(t.m_Width), static_cast(t.m_Height), 32, + static_cast(t.m_Width * 4), + 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); + if (!m_CursorSurface) + { + LOGERROR("Can't create surface for cursor: %s", SDL_GetError()); + return; + } + const float scale = g_GuiScale; + if (scale != 1.0) + { + SDL_Surface* scaledSurface = SDL_CreateRGBSurface(0, + m_CursorSurface->w * scale, + m_CursorSurface->h * scale, 32, + 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); + if (!scaledSurface) + { + LOGERROR("Can't create scaled surface forcursor: %s", SDL_GetError()); + return; + } + if (SDL_BlitScaled(m_CursorSurface, nullptr, scaledSurface, nullptr)) + return; + SDL_FreeSurface(m_CursorSurface); + m_CursorSurface = scaledSurface; + } + m_Cursor = SDL_CreateColorCursor(m_CursorSurface, hotspotX, hotspotY); + if (!m_Cursor) + { + LOGERROR("Can't create cursor: %s", SDL_GetError()); + return; + } + + SDL_SetCursor(m_Cursor); +} + +void CVideoMode::CCursor::ResetCursor() +{ + SetCursor(DEFAULT_CURSOR_NAME); +} + CVideoMode::CVideoMode() : m_WindowedW(DEFAULT_WINDOW_W), m_WindowedH(DEFAULT_WINDOW_H), m_WindowedX(0), m_WindowedY(0) { } +CVideoMode::~CVideoMode() = default; + void CVideoMode::ReadConfig() { bool windowed = !m_ConfigFullscreen; @@ -307,6 +460,8 @@ bool CVideoMode::InitSDL() SetWindowIcon(); + m_Cursor = std::make_unique(); + return true; } @@ -325,6 +480,8 @@ void CVideoMode::Shutdown() { ENSURE(m_IsInitialised); + m_Cursor.reset(); + m_IsFullscreen = false; m_IsInitialised = false; if (m_Window) @@ -563,3 +720,15 @@ void CVideoMode::SetWindowIcon() SDL_SetWindowIcon(m_Window, iconSurface); SDL_FreeSurface(iconSurface); } + +void CVideoMode::SetCursor(const CStrW& name) +{ + if (m_Cursor) + m_Cursor->SetCursor(name); +} + +void CVideoMode::ResetCursor() +{ + if (m_Cursor) + m_Cursor->ResetCursor(); +} diff --git a/source/ps/VideoMode.h b/source/ps/VideoMode.h index cf9e2e6203..f8f1b49167 100644 --- a/source/ps/VideoMode.h +++ b/source/ps/VideoMode.h @@ -18,12 +18,17 @@ #ifndef INCLUDED_VIDEOMODE #define INCLUDED_VIDEOMODE +#include "ps/CStrForward.h" + +#include + typedef struct SDL_Window SDL_Window; class CVideoMode { public: CVideoMode(); + ~CVideoMode(); /** * Initialise the video mode, for use in an SDL-using application. @@ -88,6 +93,9 @@ public: void SetWindowIcon(); + void SetCursor(const CStrW& name); + void ResetCursor(); + private: void ReadConfig(); int GetBestBPP(); @@ -135,6 +143,9 @@ private: int m_CurrentW; int m_CurrentH; int m_CurrentBPP; + + class CCursor; + std::unique_ptr m_Cursor; }; extern CVideoMode g_VideoMode; diff --git a/source/tools/atlas/GameInterface/GameLoop.cpp b/source/tools/atlas/GameInterface/GameLoop.cpp index 45673da778..e4eb3a300f 100644 --- a/source/tools/atlas/GameInterface/GameLoop.cpp +++ b/source/tools/atlas/GameInterface/GameLoop.cpp @@ -126,10 +126,6 @@ bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll) RegisterHandlers(); - // Disable the game's cursor rendering - extern CStrW g_CursorName; - g_CursorName = L""; - state.args = args; state.running = true; state.view = AtlasView::GetView_None();