icon name shortening and rudimentary directory visualization
[vrfileman] / src / fs.cc
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <string>
6 #include <map>
7 #include <algorithm>
8 #include <unistd.h>
9 #include <dirent.h>
10 #include <sys/stat.h>
11 #include "fs.h"
12 #include "icon.h"
13 #include "gmath/gmath.h"
14 #include "opengl.h"
15 #include "app.h"
16 #include "drawtext.h"
17 #include "sdr.h"
18
19 static void draw_node_name(FSNode *node, float angle, float ypos, float dist, bool full);
20
21 static IconRenderer *iconrend;
22
23 static std::map<std::string, FSNode*> node_cache;
24 static FSNode *cur_node;
25 static int start_child;
26
27 static dtx_font *fat_font;
28 #define FAT_FONT_SZ     32
29 static unsigned int font_sdr;
30
31
32 bool init_fs(const char *path)
33 {
34         iconrend = new ShapesIcons;
35         if(!iconrend->init()) {
36                 return false;
37         }
38
39         if(!(fat_font = dtx_open_font_glyphmap("data/fat.glyphmap")) ||
40                         dtx_get_glyphmap_ptsize(dtx_get_glyphmap(fat_font, 0)) != FAT_FONT_SZ) {
41
42                 dtx_set(DTX_PADDING, 64);
43
44                 if(!(fat_font = dtx_open_font("data/fat.font", 0))) {
45                         fprintf(stderr, "failed to open font file data/fat.font\n");
46                         return false;
47                 }
48                 dtx_prepare_range(fat_font, FAT_FONT_SZ * 8, 32, 127);
49                 dtx_calc_font_distfield(fat_font, 1, 8);
50                 dtx_save_glyphmap("data/fat.glyphmap", dtx_get_glyphmap(fat_font, 0));
51         }
52         dtx_use_font(fat_font, FAT_FONT_SZ);
53
54         if(!(font_sdr = create_program_load("sdr/dfont.v.glsl", "sdr/dfont.p.glsl"))) {
55                 return false;
56         }
57         set_uniform_float(font_sdr, "smoothness", 0.01);
58
59         if(!(cur_node = get_fsnode(path))) {
60                 return false;
61         }
62         cur_node->expand();
63         return true;
64 }
65
66 void cleanup_fs()
67 {
68         std::map<std::string, FSNode*>::iterator it = node_cache.begin();
69         while(it != node_cache.end()) {
70                 FSNode *node = it++->second;
71                 delete node;
72         }
73         node_cache.clear();
74         dtx_close_font(fat_font);
75         delete iconrend;
76 }
77
78 static float icon_angle(int col, int ncols, float max_angle = 0.0f)
79 {
80         if(max_angle > 0) {
81                 return max_angle * ((float)col / (float)(ncols - 1) - 0.5);
82         }
83         return 2.0 * M_PI * (float)col / (float)ncols;
84 }
85
86 void draw_fs()
87 {
88         static const float row_spacing = 0.25;
89         static const float radius = 0.6;
90         static const float umax = 0.42;
91         static const float max_icon_angle = M_PI * 2.0 * umax;
92
93         int max_ncols = std::max<int>(1, umax * 12);
94
95         glPushMatrix();
96         glTranslatef(0, cam_height, 0);
97
98         Mat4 rot_xform;
99         rot_xform.rotate(time_sec, 0, 0);
100         rot_xform.rotate(0, 0, time_sec * 0.5);
101
102         glDisable(GL_TEXTURE_2D);
103
104         int nchildren = (int)cur_node->children.size();
105         int ncols = std::min(cur_node->nfiles, max_ncols);
106
107         int first = start_child % ncols;
108         int col = 0, row = 0;
109         int num_dirs = 0;
110
111         // count directories ...
112         for(int i=0; i<nchildren; i++) {
113                 FSNode *node = cur_node->children[i];
114                 if(node->type == FSTYPE_DIR) {
115                         ++num_dirs;
116                 }
117         }
118
119         // ... and draw them
120         glLineWidth(5.0);
121         for(int i=0; i<nchildren; i++) {
122                 FSNode *node = cur_node->children[i];
123
124                 if(node->type != FSTYPE_DIR) {
125                         continue;
126                 }
127
128                 float angle = (float)col++ / (float)(num_dirs - 1) * max_icon_angle - max_icon_angle * 0.5;
129
130                 Mat4 xform;
131                 xform.rotate_y(angle);
132                 xform.translate(0, -0.3, 0);
133
134                 glUseProgram(0);
135                 glPushMatrix();
136                 glMultMatrixf(xform[0]);
137
138                 glBegin(GL_LINES);
139                 glColor3f(0.2, 0.3, 0.8);
140                 glVertex3f(0, 0, -0.3);
141                 glVertex3f(0, 0, -2);
142                 glColor3f(1, 1, 1);
143                 glEnd();
144                 glPopMatrix();
145
146                 draw_node_name(node, angle, -0.3, radius, false);
147         }
148         glLineWidth(1.0);
149
150         // then draw files
151         col = 0;
152         for(int i=0; i<nchildren; i++) {
153                 int idx = (i + first) % nchildren;
154                 FSNode *node = cur_node->children[idx];
155
156                 if(node->type == FSTYPE_DIR) {
157                         ++num_dirs;
158                         continue;
159                 }
160
161                 glUseProgram(0);
162
163                 float angle = icon_angle(col, ncols, max_icon_angle);
164
165                 Mat4 xform = rot_xform;
166                 xform.translate(0, row * row_spacing, -radius);
167                 xform.rotate_y(angle);
168
169                 glPushMatrix();
170                 glMultMatrixf(xform[0]);
171                 iconrend->draw(node);
172                 glPopMatrix();
173
174                 draw_node_name(node, angle, row * row_spacing - 0.1, radius, false);
175
176                 if(++col >= ncols) {
177                         col = 0;
178                         ++row;
179                 }
180         }
181
182         glPopMatrix();
183 }
184
185 static void draw_node_name(FSNode *node, float angle, float ypos, float dist, bool full)
186 {
187         dtx_use_font(fat_font, FAT_FONT_SZ);
188         int line_height = dtx_line_height();
189
190         int nlines = full ? node->name_lines.size() : 1;
191         for(int i=0; i<nlines; i++) {
192                 const char *name = full ? node->name_lines[i].c_str() : node->short_name.c_str();
193                 glPushMatrix();
194                 Mat4 xform;
195                 xform.translate(-dtx_string_width(name) / 2.0, -line_height * i, 0);
196                 if(node->type == FSTYPE_DIR) {
197                         xform.rotate_z(deg_to_rad(90));
198                         xform.rotate_x(deg_to_rad(-90));
199                         xform.scale(0.0017);
200                 } else {
201                         xform.scale(0.0012);
202                 }
203                 xform.translate(0, ypos, -dist);
204                 xform.rotate_y(angle);
205                 glMultMatrixf(xform[0]);
206
207                 glUseProgram(font_sdr);
208                 set_uniform_float(font_sdr, "height", line_height);
209                 dtx_string(name);
210                 glPopMatrix();
211         }
212 }
213
214 #define MAX_NAME_SZ     16
215
216 FSNode *get_fsnode(const char *path)
217 {
218         char *abspath = make_abs_path(path);
219
220         FSNode *node = node_cache[abspath];
221         if(!node) {
222                 node = new FSNode;
223                 node->path = path;
224
225                 const char *name = node->path.get_name();
226                 if(name) {
227                         const char *ptr = name;
228                         while(*ptr) {
229                                 if(ptr - name >= MAX_NAME_SZ) {
230                                         int len = ptr - name;
231                                         std::string s = std::string(name, len);
232                                         if(node->short_name.empty()) {
233                                                 node->short_name = s;
234                                                 node->short_name[len - 1] = node->short_name[len - 2] = '.';
235                                         }
236                                         node->name_lines.push_back(s);
237                                         name = ptr;
238                                 }
239                                 ++ptr;
240                         }
241                         if(*name) {
242                                 if(node->short_name.empty()) {
243                                         node->short_name = name;
244                                 }
245                                 node->name_lines.push_back(name);
246                         }
247                 }
248
249                 struct stat st;
250                 if(stat(node->path, &st) == -1) {
251                         fprintf(stderr, "failed to stat: %s\n", node->path.get_path());
252                         delete node;
253                         return 0;
254                 }
255                 node->size = st.st_size;
256
257                 switch(st.st_mode & S_IFMT) {
258                 case S_IFREG:
259                         node->type = FSTYPE_FILE;
260                         break;
261
262                 case S_IFDIR:
263                         node->type = FSTYPE_DIR;
264                         break;
265
266                 case S_IFBLK:
267                 case S_IFCHR:
268                         node->type = FSTYPE_DEV;
269                         break;
270
271                 default:
272                         node->type = FSTYPE_UNKNOWN;
273                 }
274                 node_cache[abspath] = node;
275         }
276
277         return node;
278 }
279
280 FSNode *get_fsnode(const char *dir, const char *name)
281 {
282         if(!dir) {
283                 return get_fsnode(name);
284         }
285         if(!name || *name == '/') {
286                 return 0;
287         }
288
289         int len = strlen(dir) + 1 + strlen(name);
290         char *buf = new char[len + 1];
291         sprintf(buf, "%s/%s", dir, name);
292         FSNode *res = get_fsnode(buf);
293         delete [] buf;
294         return res;
295 }
296
297 // ---- FSNode implementation ----
298 FSNode::FSNode()
299 {
300         type = FSTYPE_UNKNOWN;
301         size = 0;
302         parent = 0;
303         nfiles = ndirs = 0;
304 }
305
306 bool FSNode::expand()
307 {
308         if(type != FSTYPE_DIR) {
309                 return false;
310         }
311
312         DIR *dir = opendir(path);
313         if(!dir) {
314                 fprintf(stderr, "failed to open dir: %s: %s\n", path.get_path(), strerror(errno));
315                 return false;
316         }
317
318         struct dirent *dent;
319         while((dent = readdir(dir))) {
320                 if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) {
321                         continue;
322                 }
323
324                 FSNode *node = get_fsnode(path, dent->d_name);
325                 if(!node) continue;
326
327                 children.push_back(node);
328                 switch(node->type) {
329                 case FSTYPE_FILE:
330                         ++nfiles;
331                         break;
332                 case FSTYPE_DIR:
333                         ++ndirs;
334                 default:
335                         break;
336                 }
337         }
338         printf("expanded %d children\n", (int)children.size());
339
340         parent = get_fsnode(path.get_parent());
341         return true;
342 }