/** * ========================================================================= * File : cursor.cpp * Project : 0 A.D. * Description : mouse cursors (either via OpenGL texture or hardware) * * @author Jan.Wassenberg@stud.uni-karlsruhe.de * ========================================================================= */ /* * Copyright (c) 2003-2004 Jan Wassenberg * * Redistribution and/or modification are also permitted under the * terms of the GNU General Public License as published by the * Free Software Foundation (version 2 or later, at your option). * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #include "precompiled.h" #include #include // 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 # define ALLOW_SYS_CURSOR 1 #else # define ALLOW_SYS_CURSOR 0 #endif #include "lib/ogl.h" #include "lib/sysdep/sysdep.h" // sys_cursor_* #include "lib/res/res.h" #include "ogl_tex.h" #include "cursor.h" /* This is used to create the sys cursor to use together with the OpenGL cursor. I.e. to set a transparent cursor on X-windows where we don't use the X11 cursor, and on windows should the hardware cursor setup fail. Shouldn't be called when both hardware/software cursor fails (i.e. invalid cursor file given) - in that case we'd rather use the default cursor. */ static void *load_empty_sys_cursor() { void *sys_cursor = 0; if(sys_cursor_create_empty(&sys_cursor) < 0) { debug_warn("sys_cursor_create_empty failed"); return NULL; } return sys_cursor; } static void* load_sys_cursor(const char* filename, int hx, int hy) { #if !ALLOW_SYS_CURSOR UNUSED2(filename); UNUSED2(hx); UNUSED2(hy); return 0; #else Tex t; if(tex_load(filename, &t) < 0) return 0; { void* sys_cursor = 0; // return value // convert to required BGRA format. const uint flags = (t.flags | TEX_BGR) & ~TEX_DXT; if(tex_transform_to(&t, flags) < 0) goto fail; void* bgra_img = tex_get_data(&t); if(!bgra_img) goto fail; if(sys_cursor_create(t.w, t.h, bgra_img, hx, hy, &sys_cursor) < 0) goto fail; (void)tex_free(&t); return sys_cursor; } fail: debug_warn("failed"); (void)tex_free(&t); return 0; #endif } // no init is necessary because this is stored in struct Cursor, which // is 0-initialized by h_mgr. class GLCursor { Handle ht; uint w, h; uint hotspotx, hotspoty; public: LibError create(const char* filename, uint hotspotx_, uint hotspoty_) { ht = ogl_tex_load(filename); RETURN_ERR(ht); (void)ogl_tex_get_size(ht, &w, &h, 0); 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(uint x, uint y) const { (void)ogl_tex_bind(ht); glEnable(GL_TEXTURE_2D); 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); // OpenGL's coordinate system is "upside-down"; correct for that. y = g_yres - y; glBegin(GL_QUADS); glTexCoord2i(0, 0); glVertex2i( x-hotspotx, y+hotspoty ); glTexCoord2i(1, 0); glVertex2i( x-hotspotx+w, y+hotspoty ); glTexCoord2i(1, 1); glVertex2i( x-hotspotx+w, y+hotspoty-h ); glTexCoord2i(0, 1); glVertex2i( x-hotspotx, y+hotspoty-h ); glEnd(); } LibError validate() const { const uint 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; } }; struct Cursor { void* sys_cursor; // valid iff sys_cursor == 0. GLCursor gl_cursor; // a system cursor to use together with the gl_cursor void *gl_sys_cursor; }; H_TYPE_DEFINE(Cursor); static void Cursor_init(Cursor* UNUSED(c), va_list UNUSED(args)) { } static void Cursor_dtor(Cursor* c) { // (note: these are safe, no need for an is_valid flag) if(c->sys_cursor) WARN_ERR(sys_cursor_free(c->sys_cursor)); else c->gl_cursor.destroy(); } static LibError Cursor_reload(Cursor* c, const char* name, Handle) { char filename[PATH_MAX]; // read pixel offset of the cursor's hotspot [the bit of it that's // drawn at (g_mouse_x,g_mouse_y)] from file. uint hotspotx = 0, hotspoty = 0; { snprintf(filename, ARRAY_SIZE(filename), "art/textures/cursors/%s.txt", name); FileIOBuf buf; size_t size; RETURN_ERR(vfs_load(filename, buf, size)); std::stringstream s(std::string((const char*)buf, size)); s >> hotspotx >> hotspoty; (void)file_buf_free(buf); } // load actual cursor snprintf(filename, ARRAY_SIZE(filename), "art/textures/cursors/%s.dds", name); // .. try loading as system cursor (2d, hardware accelerated) c->sys_cursor = load_sys_cursor(filename, hotspotx, hotspoty); // .. fall back to GLCursor (system cursor code is disabled or failed) if(!c->sys_cursor) { LibError err=c->gl_cursor.create(filename, hotspotx, hotspoty); if (err == INFO_OK) c->gl_sys_cursor = load_empty_sys_cursor(); return err; } return INFO_OK; } static LibError Cursor_validate(const Cursor* c) { // note: system cursors have no state to speak of, so we don't need to // validate them. if(!c->sys_cursor) RETURN_ERR(c->gl_cursor.validate()); return INFO_OK; } static LibError Cursor_to_string(const Cursor* c, char* buf) { const char* type = c->sys_cursor? "sys" : "gl"; snprintf(buf, H_STRING_LEN, "(%s)", 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 char* name) { return h_alloc(H_Cursor, name, 0); } static LibError cursor_free(Handle& h) { return h_free(h, H_Cursor); } // draw the specified cursor at the given pixel coordinates // (origin is top-left to match the windowing system). // uses a hardware mouse cursor where available, otherwise a // portable OpenGL implementation. LibError cursor_draw(const char* name, int x, int y) { // Use 'null' to disable the cursor if(!name) { WARN_ERR(sys_cursor_set(0)); return INFO_OK; } Handle hc = cursor_load(name); H_DEREF(hc, Cursor, c); if(c->sys_cursor) WARN_ERR(sys_cursor_set(c->sys_cursor)); else { c->gl_cursor.draw(x, y); // Here, gl_sys_cursor is either a pointer to a valid cursor or NULL. // It is NULL if the gl_cursor init failed or if load_empty_sys_cursor // failed - in the first case, we want to use the default system cursor, // in the second case setting the default cursor yields no change. WARN_ERR(sys_cursor_set(c->gl_sys_cursor)); } (void)cursor_free(hc); return INFO_OK; }