doing stuff
[ld42_outofspace] / src / goatkit / theme.cc
1 /*
2 GoatKit - a themable/animated widget toolkit for games
3 Copyright (C) 2014-2018 John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include <stdio.h>
19 #include <vector>
20 #include <string>
21 #include <map>
22 #include <algorithm>
23 #include <string.h>
24 #include "theme.h"
25 #include "widget.h"
26
27 #ifdef WIN32
28 #include <windows.h>
29
30 #define RTLD_LAZY       0
31
32 static void *dlopen(const char *name, int flags);
33 static void dlclose(void *so);
34 static void *dlsym(void *so, const char *symbol);
35 #else
36 #include <unistd.h>
37 #include <dlfcn.h>
38 #endif
39
40 #ifdef HAVE_OPENGL_H
41 #include "opengl.h"
42
43 #else
44
45 #ifdef __APPLE__
46 #include <OpenGL/gl.h>
47 #else
48 #include <GL/gl.h>
49 #endif
50
51 #endif  /* HAVE_OPENGL_H_ */
52
53 #ifndef PREFIX
54 #define PREFIX  "/usr/local"
55 #endif
56
57 namespace goatkit {
58
59 struct ThemeImpl {
60         void *so;
61         WidgetDrawFunc (*lookup_theme_draw_func)(const char*);
62         mutable std::map<std::string, WidgetDrawFunc> func_cache;
63 };
64
65 Theme *theme;
66 static std::vector<std::string> search_paths;
67 static const char *fallback_paths[] = {
68         PREFIX "/share/goatkit",
69         0
70 };
71
72 typedef std::map<std::string, Theme*> ThemeMap;
73 static ThemeMap *themes;
74
75
76 void add_theme_path(const char *path)
77 {
78         if(!path || !*path) return;
79
80         std::string s = path;
81         int last = s.length() - 1;
82         if(s[last] == '/' || s[last] == '\\') {
83                 s.erase(last);
84         }
85
86         if(std::find(search_paths.begin(), search_paths.end(), s) != search_paths.end()) {
87                 return;
88         }
89
90         search_paths.push_back(s);
91 }
92
93 void register_theme(const char *name, Theme *theme)
94 {
95         if(!themes) {
96                 themes = new ThemeMap;
97         }
98
99         Theme *prev = (*themes)[name];
100         if(prev) {
101                 delete prev;
102         }
103         (*themes)[name] = theme;
104 }
105
106 Theme *get_theme(const char *name)
107 {
108         // first search in the already registered themes
109         ThemeMap::const_iterator it = themes->find(name);
110         if(it != themes->end()) {
111                 return it->second;
112         }
113
114         // then try loading it from a theme plugin
115         Theme *theme = new Theme;
116         if(theme->load(name)) {
117                 return theme;
118         }
119
120         fprintf(stderr, "[goatkit] theme \"%s\" not found!\n", name);
121         delete theme;
122         return 0;
123 }
124
125 Theme::Theme()
126 {
127         impl = new ThemeImpl;
128         impl->so = 0;
129         impl->lookup_theme_draw_func = 0;
130 }
131
132 Theme::Theme(const char *name, WidgetLookupFunc func)
133 {
134         impl = new ThemeImpl;
135         impl->so = 0;
136         impl->lookup_theme_draw_func = func;
137
138         register_theme(name, this);
139 }
140
141 Theme::~Theme()
142 {
143         unload();
144         delete impl;
145 }
146
147 typedef WidgetDrawFunc (*LookupFunc)(const char*);
148
149 bool Theme::load(const char *name)
150 {
151         unload();
152
153         std::string fname = std::string(name) + ".gtheme";
154         if(!(impl->so = dlopen(fname.c_str(), RTLD_LAZY))) {
155                 for(size_t i=0; i<search_paths.size(); i++) {
156                         std::string path = search_paths[i] + "/" + fname;
157
158                         if((impl->so = dlopen(path.c_str(), RTLD_LAZY))) {
159                                 break;
160                         }
161                 }
162
163                 // try the fallback paths
164                 if(!impl->so) {
165                         for(int i=0; fallback_paths[i]; i++) {
166                                 std::string path = std::string(fallback_paths[i]) + "/" + fname;
167
168                                 if((impl->so = dlopen(path.c_str(), RTLD_LAZY))) {
169                                         break;
170                                 }
171                         }
172                 }
173
174                 if(!impl->so) {
175                         fprintf(stderr, "%s: failed to load theme plugin: %s\n", __func__, name);
176                         return false;
177                 }
178         }
179
180         // loaded the shared object, now get the lookup function
181         impl->lookup_theme_draw_func = (LookupFunc)dlsym(impl->so, "get_widget_func");
182         if(!impl->lookup_theme_draw_func) {
183                 fprintf(stderr, "%s: invalid theme plugin %s\n", __func__, name);
184                 unload();
185                 return false;
186         }
187
188         register_theme(name, this);
189         return true;
190 }
191
192 void Theme::unload()
193 {
194         if(impl->so) {
195                 dlclose(impl->so);
196                 impl->so = 0;
197         }
198         impl->func_cache.clear();
199 }
200
201 WidgetDrawFunc Theme::get_draw_func(const char *type) const
202 {
203         std::map<std::string, WidgetDrawFunc>::const_iterator it = impl->func_cache.find(type);
204         if(it == impl->func_cache.end()) {
205                 // don't have it cached, try to look it up
206                 WidgetDrawFunc func;
207                 if(impl->lookup_theme_draw_func && (func = impl->lookup_theme_draw_func(type))) {
208                         impl->func_cache[type] = func;
209                         return func;
210                 }
211
212                 // can't look it up, return the default
213                 return default_draw_func;
214         }
215         return it->second;
216 }
217
218 #define LERP(a, b, t)   ((a) + ((b) - (a)) * t)
219 #define DEF_TEX_SZ      32
220 void default_draw_func(const Widget *w)
221 {
222         static unsigned int tex;
223
224         if(!tex) {
225                 unsigned char *pixels = new unsigned char[DEF_TEX_SZ * DEF_TEX_SZ * 3];
226                 unsigned char *ptr = pixels;
227                 for(int i=0; i<DEF_TEX_SZ; i++) {
228                         for(int j=0; j<DEF_TEX_SZ; j++) {
229                                 bool stripe = (((j + i) / 8) & 1) == 1;
230                                 ptr[0] = ptr[1] = ptr[2] = stripe ? 255 : 0;
231                                 ptr += 3;
232                         }
233                 }
234
235                 glGenTextures(1, &tex);
236                 glBindTexture(GL_TEXTURE_2D, tex);
237                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
238                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
239                 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, DEF_TEX_SZ, DEF_TEX_SZ, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
240                 delete [] pixels;
241         }
242
243         Vec2 pos = w->get_position();
244         Vec2 sz = w->get_size();
245         float aspect = sz.x / sz.y;
246
247 #if !defined(GL_ES_VERSION_2_0)
248         glPushAttrib(GL_ENABLE_BIT);
249         glEnable(GL_TEXTURE_2D);
250 #endif
251         glBindTexture(GL_TEXTURE_2D, tex);
252
253         float offs = w->get_pressed() * 0.1 * sz.y;
254         glMatrixMode(GL_MODELVIEW);
255         glPushMatrix();
256         glTranslatef(offs, -offs, 0);
257
258         float active = w->get_active();
259         float hover = w->get_under_mouse();
260
261         float rg = LERP(0.4, 1.0, hover);
262         float b = LERP(rg, 0, active);
263         glColor3f(rg, rg, b);
264
265         glBegin(GL_QUADS);
266         glTexCoord2f(0, 1);
267         glVertex2f(pos.x, pos.y);
268         glTexCoord2f(aspect, 1);
269         glVertex2f(pos.x + sz.x, pos.y);
270         glTexCoord2f(aspect, 0);
271         glVertex2f(pos.x + sz.x, pos.y + sz.y);
272         glTexCoord2f(0, 0);
273         glVertex2f(pos.x, pos.y + sz.y);
274         glEnd();
275
276         glPopMatrix();
277
278 #ifndef GL_ES_VERSION_2_0
279         glPopAttrib();
280 #endif
281 }
282
283 }       // namespace goatkit
284
285 #ifdef WIN32
286 // XXX untested
287 static void *dlopen(const char *name, int flags)
288 {
289         return LoadLibrary(name);
290 }
291
292 static void dlclose(void *so)
293 {
294         FreeLibrary((HMODULE)so);
295 }
296
297 static void *dlsym(void *so, const char *symbol)
298 {
299         if(!so) {
300                 so = GetModuleHandle(0);
301         }
302         return (void*)GetProcAddress((HMODULE)so, symbol);
303 }
304 #endif