all repos — mgba @ 797441c379af788b89968964fcddf219afa42487

mGBA Game Boy Advance Emulator

src/util/gui/file-select.c (view raw)

  1/* Copyright (c) 2013-2015 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "file-select.h"
  7
  8#include "util/gui/font.h"
  9#include "util/vector.h"
 10#include "util/vfs.h"
 11
 12DECLARE_VECTOR(FileList, char*);
 13DEFINE_VECTOR(FileList, char*);
 14
 15#define ITERATION_SIZE 5
 16#define SCANNING_THRESHOLD 20
 17
 18static void _cleanFiles(struct FileList* currentFiles) {
 19	size_t size = FileListSize(currentFiles);
 20	size_t i;
 21	for (i = 1; i < size; ++i) {
 22		free(*FileListGetPointer(currentFiles, i));
 23	}
 24	FileListClear(currentFiles);
 25}
 26
 27static void _upDirectory(char* currentPath) {
 28	char* end = strrchr(currentPath, '/');
 29	if (!end) {
 30		return;
 31	}
 32	if (end == currentPath) {
 33		end[1] = '\0';
 34		return;
 35	}
 36	end[0] = '\0';
 37	if (end[1]) {
 38		return;
 39	}
 40	// TODO: What if there was a trailing slash?
 41}
 42
 43static bool _refreshDirectory(const struct GUIParams* params, const char* currentPath, struct FileList* currentFiles, bool (*filter)(struct VFile*)) {
 44	_cleanFiles(currentFiles);
 45
 46	struct VDir* dir = VDirOpen(currentPath);
 47	if (!dir) {
 48		return false;
 49	}
 50	*FileListAppend(currentFiles) = "(Up)";
 51	size_t i = 0;
 52	struct VDirEntry* de;
 53	while ((de = dir->listNext(dir))) {
 54		++i;
 55		if (i == SCANNING_THRESHOLD) {
 56			params->drawStart();
 57			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath);
 58			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning)");
 59			params->drawEnd();
 60		}
 61		const char* name = de->name(de);
 62		if (name[0] == '.') {
 63			continue;
 64		}
 65		if (de->type(de) == VFS_FILE) {
 66			struct VFile* vf = dir->openFile(dir, name, O_RDONLY);
 67			if (!vf) {
 68				continue;
 69			}
 70			if (!filter || filter(vf)) {
 71				*FileListAppend(currentFiles) = strdup(name);
 72			}
 73			vf->close(vf);
 74		} else {
 75			*FileListAppend(currentFiles) = strdup(name);
 76		}
 77	}
 78	dir->close(dir);
 79	return true;
 80}
 81
 82bool selectFile(const struct GUIParams* params, const char* basePath, char* outPath, char* currentPath, size_t outLen, bool (*filter)(struct VFile*)) {
 83	if (!currentPath[0]) {
 84		strncpy(currentPath, basePath, outLen);
 85	}
 86	size_t fileIndex = 0;
 87	size_t start = 0;
 88
 89	struct FileList currentFiles;
 90	FileListInit(&currentFiles, 0);
 91	_refreshDirectory(params, currentPath, &currentFiles, filter);
 92
 93	int inputHistory[GUI_INPUT_MAX] = { 0 };
 94
 95	while (true) {
 96		int input = params->pollInput();
 97		int newInput = 0;
 98		for (int i = 0; i < GUI_INPUT_MAX; ++i) {
 99			if (input & (1 << i)) {
100				++inputHistory[i];
101			} else {
102				inputHistory[i] = -1;
103			}
104			if (!inputHistory[i] || (inputHistory[i] >= 30 && !(inputHistory[i] % 6))) {
105				newInput |= (1 << i);
106			}
107		}
108
109		if (newInput & (1 << GUI_INPUT_UP) && fileIndex > 0) {
110			--fileIndex;
111		}
112		if (newInput & (1 << GUI_INPUT_DOWN) && fileIndex < FileListSize(&currentFiles) - 1) {
113			++fileIndex;
114		}
115		if (newInput & (1 << GUI_INPUT_LEFT)) {
116			if (fileIndex >= ITERATION_SIZE) {
117				fileIndex -= ITERATION_SIZE;
118			} else {
119				fileIndex = 0;
120			}
121		}
122		if (newInput & (1 << GUI_INPUT_RIGHT)) {
123			if (fileIndex + ITERATION_SIZE < FileListSize(&currentFiles)) {
124				fileIndex += ITERATION_SIZE;
125			} else {
126				fileIndex = FileListSize(&currentFiles) - 1;
127			}
128		}
129
130		if (fileIndex < start) {
131			start = fileIndex;
132		}
133		while ((fileIndex - start + 4) * GUIFontHeight(params->font) > params->height) {
134			++start;
135		}
136		if (newInput & (1 << GUI_INPUT_CANCEL)) {
137			_cleanFiles(&currentFiles);
138			FileListDeinit(&currentFiles);
139			return false;
140		}
141		if (newInput & (1 << GUI_INPUT_SELECT)) {
142			if (fileIndex == 0) {
143				_upDirectory(currentPath);
144				_refreshDirectory(params, currentPath, &currentFiles, filter);
145			} else {
146				size_t len = strlen(currentPath);
147				const char* sep = PATH_SEP;
148				if (currentPath[len - 1] == *sep) {
149					sep = "";
150				}
151				snprintf(outPath, outLen, "%s%s%s", currentPath, sep, *FileListGetPointer(&currentFiles, fileIndex));
152				if (!_refreshDirectory(params, outPath, &currentFiles, filter)) {
153					return true;
154				}
155				strncpy(currentPath, outPath, outLen);
156			}
157			fileIndex = 0;
158		}
159		if (newInput & (1 << GUI_INPUT_BACK)) {
160			if (strncmp(currentPath, basePath, outLen) == 0) {
161				_cleanFiles(&currentFiles);
162				FileListDeinit(&currentFiles);
163				return false;
164			}
165			_upDirectory(currentPath);
166			_refreshDirectory(params, currentPath, &currentFiles, filter);
167			fileIndex = 0;
168		}
169
170		params->drawStart();
171		unsigned y = GUIFontHeight(params->font);
172		GUIFontPrintf(params->font, 0, y, GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath);
173		y += 2 * GUIFontHeight(params->font);
174		size_t i;
175		for (i = start; i < FileListSize(&currentFiles); ++i) {
176			int color = 0xE0A0A0A0;
177			char bullet = ' ';
178			if (i == fileIndex) {
179				color = 0xFFFFFFFF;
180				bullet = '>';
181			}
182			GUIFontPrintf(params->font, 0, y, GUI_TEXT_LEFT, color, "%c %s", bullet, *FileListGetPointer(&currentFiles, i));
183			y += GUIFontHeight(params->font);
184			if (y + GUIFontHeight(params->font) > params->height) {
185				break;
186			}
187		}
188		y += GUIFontHeight(params->font) * 2;
189
190		params->drawEnd();
191	}
192}