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