Line data Source code
1 : /* Copyright (C) 2020 Wildfire Games.
2 : *
3 : * Permission is hereby granted, free of charge, to any person obtaining
4 : * a copy of this software and associated documentation files (the
5 : * "Software"), to deal in the Software without restriction, including
6 : * without limitation the rights to use, copy, modify, merge, publish,
7 : * distribute, sublicense, and/or sell copies of the Software, and to
8 : * permit persons to whom the Software is furnished to do so, subject to
9 : * the following conditions:
10 : *
11 : * The above copyright notice and this permission notice shall be included
12 : * in all copies or substantial portions of the Software.
13 : *
14 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 : * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 : * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 : * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 : * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 : * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 : * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 : */
22 :
23 : /*
24 : * mouse cursors (either via OpenGL texture or hardware)
25 : */
26 :
27 : #include "precompiled.h"
28 : #include "cursor.h"
29 :
30 : #include <cstdio>
31 : #include <cstring>
32 : #include <sstream>
33 :
34 : #include "lib/external_libraries/libsdl.h"
35 : #include "lib/ogl.h"
36 : #include "lib/res/h_mgr.h"
37 : #include "ogl_tex.h"
38 :
39 : class SDLCursor
40 : {
41 : SDL_Surface* surface;
42 : SDL_Cursor* cursor;
43 :
44 : public:
45 0 : Status create(const PIVFS& vfs, const VfsPath& pathname, int hotspotx_, int hotspoty_, double scale)
46 : {
47 0 : std::shared_ptr<u8> file; size_t fileSize;
48 0 : RETURN_STATUS_IF_ERR(vfs->LoadFile(pathname, file, fileSize));
49 :
50 0 : Tex t;
51 0 : RETURN_STATUS_IF_ERR(t.decode(file, fileSize));
52 :
53 : // convert to required BGRA format.
54 0 : const size_t flags = (t.m_Flags | TEX_BGR) & ~TEX_DXT;
55 0 : RETURN_STATUS_IF_ERR(t.transform_to(flags));
56 0 : void* bgra_img = t.get_data();
57 0 : if(!bgra_img)
58 0 : WARN_RETURN(ERR::FAIL);
59 :
60 0 : surface = SDL_CreateRGBSurfaceFrom(bgra_img, (int)t.m_Width, (int)t.m_Height, 32, (int)t.m_Width*4, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
61 0 : if(!surface)
62 : return ERR::FAIL;
63 0 : if(scale != 1.0)
64 : {
65 0 : SDL_Surface* scaled_surface = SDL_CreateRGBSurface(0, surface->w * scale, surface->h * scale, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
66 0 : if(!scaled_surface)
67 : return ERR::FAIL;
68 0 : if(SDL_BlitScaled(surface, NULL, scaled_surface, NULL))
69 : return ERR::FAIL;
70 0 : SDL_FreeSurface(surface);
71 0 : surface = scaled_surface;
72 : }
73 0 : cursor = SDL_CreateColorCursor(surface, hotspotx_, hotspoty_);
74 0 : if(!cursor)
75 0 : return ERR::FAIL;
76 :
77 : return INFO::OK;
78 : }
79 :
80 0 : void set()
81 : {
82 0 : SDL_SetCursor(cursor);
83 0 : }
84 :
85 0 : void destroy()
86 : {
87 0 : SDL_FreeCursor(cursor);
88 0 : SDL_FreeSurface(surface);
89 0 : }
90 : };
91 :
92 : // no init is necessary because this is stored in struct Cursor, which
93 : // is 0-initialized by h_mgr.
94 : class GLCursor
95 : {
96 : Handle ht;
97 :
98 : GLint w, h;
99 : int hotspotx, hotspoty;
100 :
101 : public:
102 0 : Status create(const PIVFS& vfs, const VfsPath& pathname, int hotspotx_, int hotspoty_, double scale)
103 : {
104 0 : ht = ogl_tex_load(vfs, pathname);
105 0 : RETURN_STATUS_IF_ERR(ht);
106 :
107 0 : size_t width, height;
108 0 : (void)ogl_tex_get_size(ht, &width, &height, 0);
109 0 : w = (GLint)(width * scale);
110 0 : h = (GLint)(height * scale);
111 :
112 0 : hotspotx = hotspotx_; hotspoty = hotspoty_;
113 :
114 0 : (void)ogl_tex_set_filter(ht, GL_NEAREST);
115 0 : (void)ogl_tex_upload(ht);
116 : return INFO::OK;
117 : }
118 :
119 : void destroy()
120 : {
121 : // note: we're stored in a resource => ht is initially 0 =>
122 : // this is safe, no need for an is_valid flag
123 0 : (void)ogl_tex_free(ht);
124 : }
125 :
126 0 : void draw(int x, int y) const
127 : {
128 : #if CONFIG2_GLES
129 : UNUSED2(x); UNUSED2(y);
130 : #warning TODO: implement cursors for GLES
131 : #else
132 0 : (void)ogl_tex_bind(ht);
133 0 : glEnable(GL_TEXTURE_2D);
134 0 : glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
135 0 : glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
136 0 : glEnable(GL_BLEND);
137 0 : glDisable(GL_DEPTH_TEST);
138 0 : glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
139 :
140 0 : glBegin(GL_QUADS);
141 0 : glTexCoord2i(1, 0); glVertex2i( x-hotspotx+w, y+hotspoty );
142 0 : glTexCoord2i(0, 0); glVertex2i( x-hotspotx, y+hotspoty );
143 0 : glTexCoord2i(0, 1); glVertex2i( x-hotspotx, y+hotspoty-h );
144 0 : glTexCoord2i(1, 1); glVertex2i( x-hotspotx+w, y+hotspoty-h );
145 0 : glEnd();
146 :
147 0 : glDisable(GL_BLEND);
148 0 : glEnable(GL_DEPTH_TEST);
149 : #endif
150 0 : }
151 :
152 0 : Status validate() const
153 : {
154 0 : const GLint A = 128; // no cursor is expected to get this big
155 0 : if(w > A || h > A || hotspotx > A || hotspoty > A)
156 0 : WARN_RETURN(ERR::_1);
157 0 : if(ht < 0)
158 0 : WARN_RETURN(ERR::_2);
159 : return INFO::OK;
160 : }
161 : };
162 :
163 : enum CursorKind
164 : {
165 : CK_Default,
166 : CK_SDL,
167 : CK_OpenGL
168 : };
169 :
170 : struct Cursor
171 : {
172 : double scale;
173 :
174 : // require kind == CK_OpenGL after reload
175 : bool forceGL;
176 :
177 : CursorKind kind;
178 :
179 : // valid iff kind == CK_SDL
180 : SDLCursor sdl_cursor;
181 :
182 : // valid iff kind == CK_OpenGL
183 : GLCursor gl_cursor;
184 : };
185 :
186 : H_TYPE_DEFINE(Cursor);
187 :
188 0 : static void Cursor_init(Cursor* c, va_list args)
189 : {
190 0 : c->scale = va_arg(args, double);
191 0 : c->forceGL = (va_arg(args, int) != 0);
192 0 : }
193 :
194 0 : static void Cursor_dtor(Cursor* c)
195 : {
196 0 : switch(c->kind)
197 : {
198 : case CK_Default:
199 : break; // nothing to do
200 :
201 0 : case CK_SDL:
202 0 : c->sdl_cursor.destroy();
203 : break;
204 :
205 0 : case CK_OpenGL:
206 0 : c->gl_cursor.destroy();
207 : break;
208 :
209 0 : default:
210 0 : DEBUG_WARN_ERR(ERR::LOGIC);
211 : break;
212 : }
213 0 : }
214 :
215 0 : static Status Cursor_reload(Cursor* c, const PIVFS& vfs, const VfsPath& name, Handle)
216 : {
217 0 : const VfsPath pathname(VfsPath(L"art/textures/cursors") / name);
218 :
219 : // read pixel offset of the cursor's hotspot [the bit of it that's
220 : // drawn at (g_mouse_x,g_mouse_y)] from file.
221 0 : int hotspotx = 0, hotspoty = 0;
222 0 : {
223 0 : const VfsPath pathnameHotspot = pathname.ChangeExtension(L".txt");
224 0 : std::shared_ptr<u8> buf; size_t size;
225 0 : RETURN_STATUS_IF_ERR(vfs->LoadFile(pathnameHotspot, buf, size));
226 0 : std::wstringstream s(std::wstring((const wchar_t*)buf.get(), size));
227 0 : s >> hotspotx >> hotspoty;
228 : }
229 :
230 0 : const VfsPath pathnameImage = pathname.ChangeExtension(L".png");
231 :
232 : // try loading as SDL2 cursor
233 0 : if(!c->forceGL && c->sdl_cursor.create(vfs, pathnameImage, hotspotx, hotspoty, c->scale) == INFO::OK)
234 0 : c->kind = CK_SDL;
235 : // fall back to GLCursor (system cursor code is disabled or failed)
236 0 : else if(c->gl_cursor.create(vfs, pathnameImage, hotspotx, hotspoty, c->scale) == INFO::OK)
237 0 : c->kind = CK_OpenGL;
238 : // everything failed, leave cursor unchanged
239 : else
240 0 : c->kind = CK_Default;
241 :
242 0 : return INFO::OK;
243 : }
244 :
245 0 : static Status Cursor_validate(const Cursor* c)
246 : {
247 0 : switch(c->kind)
248 : {
249 : case CK_Default:
250 : break; // nothing to do
251 :
252 : case CK_SDL:
253 : break; // nothing to do
254 :
255 0 : case CK_OpenGL:
256 0 : RETURN_STATUS_IF_ERR(c->gl_cursor.validate());
257 : break;
258 :
259 0 : default:
260 0 : WARN_RETURN(ERR::_2);
261 : break;
262 : }
263 :
264 : return INFO::OK;
265 : }
266 :
267 0 : static Status Cursor_to_string(const Cursor* c, wchar_t* buf)
268 : {
269 0 : const wchar_t* type;
270 0 : switch(c->kind)
271 : {
272 : case CK_Default:
273 : type = L"default";
274 : break;
275 :
276 0 : case CK_SDL:
277 0 : type = L"sdl";
278 0 : break;
279 :
280 0 : case CK_OpenGL:
281 0 : type = L"gl";
282 0 : break;
283 :
284 0 : default:
285 0 : DEBUG_WARN_ERR(ERR::LOGIC);
286 : type = L"?";
287 : break;
288 : }
289 :
290 0 : swprintf_s(buf, H_STRING_LEN, L"cursor (%ls)", type);
291 0 : return INFO::OK;
292 : }
293 :
294 :
295 : // note: these standard resource interface functions are not exposed to the
296 : // caller. all we need here is storage for the SDL_Cursor / GLCursor and
297 : // a name -> data lookup mechanism, both provided by h_mgr.
298 : // in other words, we continually create/free the cursor resource in
299 : // cursor_draw and trust h_mgr's caching to absorb it.
300 :
301 : static Handle cursor_load(const PIVFS& vfs, const VfsPath& name, double scale, bool forceGL)
302 : {
303 0 : return h_alloc(H_Cursor, vfs, name, 0, scale, (int)forceGL);
304 : }
305 :
306 0 : void cursor_shutdown()
307 : {
308 0 : h_mgr_free_type(H_Cursor);
309 0 : }
310 :
311 : static Status cursor_free(Handle& h)
312 : {
313 0 : return h_free(h, H_Cursor);
314 : }
315 :
316 :
317 0 : Status cursor_draw(const PIVFS& vfs, const wchar_t* name, int x, int y, double scale, bool forceGL)
318 : {
319 : // hide the cursor
320 0 : if(!name)
321 : {
322 0 : SDL_ShowCursor(SDL_DISABLE);
323 0 : return INFO::OK;
324 : }
325 :
326 0 : Handle hc = cursor_load(vfs, name, scale, forceGL);
327 : // TODO: if forceGL changes at runtime after a cursor is first created,
328 : // we might reuse a cached version of the cursor with the old forceGL flag
329 :
330 0 : RETURN_STATUS_IF_ERR(hc); // silently ignore failures
331 :
332 0 : H_DEREF(hc, Cursor, c);
333 :
334 0 : switch(c->kind)
335 : {
336 : case CK_Default:
337 : break;
338 :
339 0 : case CK_SDL:
340 0 : c->sdl_cursor.set();
341 0 : SDL_ShowCursor(SDL_ENABLE);
342 : break;
343 :
344 0 : case CK_OpenGL:
345 0 : c->gl_cursor.draw(x, y);
346 0 : SDL_ShowCursor(SDL_DISABLE);
347 : break;
348 :
349 0 : default:
350 0 : DEBUG_WARN_ERR(ERR::LOGIC);
351 : break;
352 : }
353 :
354 0 : (void)cursor_free(hc);
355 : return INFO::OK;
356 : }
|