mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 21:34:08 -07:00
Differential Revision: https://code.wildfiregames.com/D320 Fixes #3875 Patch By: Dariost Reviewed By: domdomegg This was SVN commit r19667.
366 lines
8.5 KiB
C++
366 lines
8.5 KiB
C++
/* 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)
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
#include "cursor.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <sstream>
|
|
|
|
#include "lib/external_libraries/libsdl.h"
|
|
#include "lib/ogl.h"
|
|
#include "lib/res/h_mgr.h"
|
|
#include "lib/sysdep/cursor.h"
|
|
#include "ogl_tex.h"
|
|
|
|
// On Windows, allow runtime choice between system cursors and OpenGL
|
|
// cursors (Windows = more responsive, OpenGL = more consistent with what
|
|
// the game sees)
|
|
#if OS_WIN || OS_UNIX
|
|
# define ALLOW_SYS_CURSOR 1
|
|
#else
|
|
# define ALLOW_SYS_CURSOR 0
|
|
#endif
|
|
|
|
class SDLCursor
|
|
{
|
|
SDL_Surface* surface;
|
|
SDL_Cursor* cursor;
|
|
|
|
public:
|
|
Status create(const PIVFS& vfs, const VfsPath& pathname, int hotspotx_, int hotspoty_, double scale)
|
|
{
|
|
shared_ptr<u8> 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");
|
|
shared_ptr<u8> 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 sys_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;
|
|
}
|