415 lines
13 KiB
C

#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <SDL_ttf.h>
#include <nfd.h>
#include <nfd_sdl2.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
// Small program meant to demonstrate and test nfd_sdl2.h with SDL2. Note that it quits immediately
// when it encounters an error, without calling the opposite destroy/quit function. A real-world
// application should call destroy/quit appropriately.
void show_error(const char* message, SDL_Window* window) {
if (SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", message, window) != 0) {
printf("SDL_ShowSimpleMessageBox failed: %s\n", SDL_GetError());
return;
}
}
void show_path(const char* path, SDL_Window* window) {
if (SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Success", path, window) != 0) {
printf("SDL_ShowSimpleMessageBox failed: %s\n", SDL_GetError());
return;
}
}
void show_paths(const nfdpathset_t* paths, SDL_Window* window) {
size_t num_chars = 0;
nfdpathsetsize_t num_paths;
if (NFD_PathSet_GetCount(paths, &num_paths) != NFD_OKAY) {
printf("NFD_PathSet_GetCount failed: %s\n", NFD_GetError());
return;
}
nfdpathsetsize_t i;
for (i = 0; i != num_paths; ++i) {
char* path;
if (NFD_PathSet_GetPathU8(paths, i, &path) != NFD_OKAY) {
printf("NFD_PathSet_GetPathU8 failed: %s\n", NFD_GetError());
return;
}
num_chars += strlen(path) + 1;
NFD_PathSet_FreePathU8(path);
}
// We should never return NFD_OKAY with zero paths, but GCC doesn't know this and will emit a
// warning that we're trying to malloc with size zero if we write the following line.
if (!num_paths) num_chars = 1;
char* message = malloc(num_chars);
message[0] = '\0';
for (i = 0; i != num_paths; ++i) {
if (i != 0) {
strcat(message, "\n");
}
char* path;
if (NFD_PathSet_GetPathU8(paths, i, &path) != NFD_OKAY) {
printf("NFD_PathSet_GetPathU8 failed: %s\n", NFD_GetError());
free(message);
return;
}
strcat(message, path);
NFD_PathSet_FreePathU8(path);
}
if (SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Success", message, window) != 0) {
printf("SDL_ShowSimpleMessageBox failed: %s\n", SDL_GetError());
free(message);
return;
}
free(message);
}
void set_native_window(SDL_Window* sdlWindow, nfdwindowhandle_t* nativeWindow) {
if (!NFD_GetNativeWindowFromSDLWindow(sdlWindow, nativeWindow)) {
printf("NFD_GetNativeWindowFromSDLWindow failed: %s\n", SDL_GetError());
}
}
void opendialog_handler(SDL_Window* window) {
char* path;
nfdopendialogu8args_t args = {0};
set_native_window(window, &args.parentWindow);
const nfdresult_t res = NFD_OpenDialogU8_With(&path, &args);
switch (res) {
case NFD_OKAY:
show_path(path, window);
NFD_FreePathU8(path);
break;
case NFD_ERROR:
show_error(NFD_GetError(), window);
break;
default:
break;
}
}
void opendialogmultiple_handler(SDL_Window* window) {
const nfdpathset_t* paths;
nfdopendialogu8args_t args = {0};
set_native_window(window, &args.parentWindow);
const nfdresult_t res = NFD_OpenDialogMultipleU8_With(&paths, &args);
switch (res) {
case NFD_OKAY:
show_paths(paths, window);
NFD_PathSet_Free(paths);
break;
case NFD_ERROR:
show_error(NFD_GetError(), window);
break;
default:
break;
}
}
void savedialog_handler(SDL_Window* window) {
char* path;
nfdsavedialogu8args_t args = {0};
set_native_window(window, &args.parentWindow);
const nfdresult_t res = NFD_SaveDialogU8_With(&path, &args);
switch (res) {
case NFD_OKAY:
show_path(path, window);
NFD_FreePathU8(path);
break;
case NFD_ERROR:
show_error(NFD_GetError(), window);
break;
default:
break;
}
}
void pickfolder_handler(SDL_Window* window) {
char* path;
nfdpickfolderu8args_t args = {0};
set_native_window(window, &args.parentWindow);
const nfdresult_t res = NFD_PickFolderU8_With(&path, &args);
switch (res) {
case NFD_OKAY:
show_path(path, window);
NFD_FreePathU8(path);
break;
case NFD_ERROR:
show_error(NFD_GetError(), window);
break;
default:
break;
}
}
void pickfoldermultiple_handler(SDL_Window* window) {
const nfdpathset_t* paths;
nfdpickfolderu8args_t args = {0};
set_native_window(window, &args.parentWindow);
const nfdresult_t res = NFD_PickFolderMultipleU8_With(&paths, &args);
switch (res) {
case NFD_OKAY:
show_paths(paths, window);
NFD_PathSet_Free(paths);
break;
case NFD_ERROR:
show_error(NFD_GetError(), window);
break;
default:
break;
}
}
#if defined(_WIN32)
const char font_file[] = "C:\\Windows\\Fonts\\calibri.ttf";
#elif defined(__APPLE__)
const char font_file[] = "/System/Library/Fonts/SFNS.ttf";
#else
const char font_file[] = "/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf";
#endif
#define NUM_STATES 3
#define NUM_BUTTONS 5
const char* button_text[NUM_BUTTONS] = {"Open File",
"Open Files",
"Save File",
"Select Folder",
"Select Folders"};
const int BUTTON_WIDTH = 400;
const int BUTTON_HEIGHT = 40;
void (*button_handler[NUM_BUTTONS])(SDL_Window*) = {&opendialog_handler,
&opendialogmultiple_handler,
&savedialog_handler,
&pickfolder_handler,
&pickfoldermultiple_handler};
#ifdef _WIN32
int WINAPI WinMain(void)
#else
int main(void)
#endif
{
#ifdef _WIN32
// Enable DPI awareness on Windows
SDL_SetHint("SDL_HINT_WINDOWS_DPI_AWARENESS", "permonitorv2");
SDL_SetHint("SDL_HINT_WINDOWS_DPI_SCALING", "1");
#endif
// initialize SDL
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
printf("SDL_Init failed: %s\n", SDL_GetError());
return 0;
}
// initialize SDL_ttf
if (TTF_Init() != 0) {
printf("TTF_Init failed: %s\n", TTF_GetError());
return 0;
}
// initialize NFD
if (NFD_Init() != NFD_OKAY) {
printf("NFD_Init failed: %s\n", NFD_GetError());
return 0;
}
// create window
SDL_Window* const window = SDL_CreateWindow("Welcome",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
BUTTON_WIDTH,
BUTTON_HEIGHT * NUM_BUTTONS,
SDL_WINDOW_ALLOW_HIGHDPI);
if (!window) {
printf("SDL_CreateWindow failed: %s\n", SDL_GetError());
return 0;
}
// create renderer
SDL_Renderer* const renderer =
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (!renderer) {
printf("SDL_CreateRenderer failed: %s\n", SDL_GetError());
return 0;
}
// prepare the buttons and handlers
SDL_Texture* textures_normal[NUM_BUTTONS][NUM_STATES];
TTF_Font* const font = TTF_OpenFont(font_file, 20);
if (!font) {
printf("TTF_OpenFont failed: %s\n", TTF_GetError());
return 0;
}
const SDL_Color back_color[NUM_STATES] = {{0, 0, 0, SDL_ALPHA_OPAQUE},
{51, 51, 51, SDL_ALPHA_OPAQUE},
{102, 102, 102, SDL_ALPHA_OPAQUE}};
const SDL_Color text_color = {255, 255, 255, SDL_ALPHA_OPAQUE};
const uint8_t text_alpha[NUM_STATES] = {153, 204, 255};
for (size_t i = 0; i != NUM_BUTTONS; ++i) {
SDL_Surface* const text_surface = TTF_RenderUTF8_Blended(font, button_text[i], text_color);
if (!text_surface) {
printf("TTF_RenderUTF8_Blended failed: %s\n", TTF_GetError());
return 0;
}
if (SDL_SetSurfaceBlendMode(text_surface, SDL_BLENDMODE_BLEND) != 0) {
printf("SDL_SetSurfaceBlendMode failed: %s\n", SDL_GetError());
return 0;
}
for (size_t j = 0; j != NUM_STATES; ++j) {
SDL_Surface* button_surface =
SDL_CreateRGBSurface(0, BUTTON_WIDTH, BUTTON_HEIGHT, 32, 0, 0, 0, 0);
if (!button_surface) {
printf("SDL_CreateRGBSurface failed: %s\n", SDL_GetError());
return 0;
}
if (SDL_FillRect(button_surface,
NULL,
SDL_MapRGBA(button_surface->format,
back_color[j].r,
back_color[j].g,
back_color[j].b,
back_color[j].a)) != 0) {
printf("SDL_FillRect failed: %s\n", SDL_GetError());
return 0;
}
SDL_SetSurfaceAlphaMod(text_surface, text_alpha[j]);
SDL_Rect dstrect = {(BUTTON_WIDTH - text_surface->w) / 2,
(BUTTON_HEIGHT - text_surface->h) / 2,
text_surface->w,
text_surface->h};
if (SDL_BlitSurface(text_surface, NULL, button_surface, &dstrect) != 0) {
printf("SDL_BlitSurface failed: %s\n", SDL_GetError());
return 0;
}
SDL_Texture* const texture = SDL_CreateTextureFromSurface(renderer, button_surface);
if (!texture) {
printf("SDL_CreateTextureFromSurface failed: %s\n", SDL_GetError());
return 0;
}
SDL_FreeSurface(button_surface);
textures_normal[i][j] = texture;
}
SDL_FreeSurface(text_surface);
}
TTF_CloseFont(font);
// event loop
bool quit = false;
size_t button_index = (size_t)-1;
bool pressed = false;
do {
// render
for (size_t i = 0; i != NUM_BUTTONS; ++i) {
const SDL_Rect rect = {0, (int)i * BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT};
SDL_RenderCopy(
renderer, textures_normal[i][button_index == i ? pressed ? 2 : 1 : 0], NULL, &rect);
}
SDL_RenderPresent(renderer);
// process events
SDL_Event event;
if (SDL_WaitEvent(&event) == 0) {
printf("SDL_WaitEvent failed: %s\n", SDL_GetError());
return 0;
}
do {
switch (event.type) {
case SDL_QUIT: {
quit = true;
break;
}
case SDL_WINDOWEVENT: {
switch (event.window.event) {
case SDL_WINDOWEVENT_CLOSE:
quit = true;
break;
case SDL_WINDOWEVENT_LEAVE:
button_index = (size_t)-1;
break;
}
break;
}
case SDL_MOUSEMOTION: {
if (event.motion.x < 0 || event.motion.x >= BUTTON_WIDTH ||
event.motion.y < 0) {
button_index = (size_t)-1;
break;
}
const int index = event.motion.y / BUTTON_HEIGHT;
if (index < 0 || index >= NUM_BUTTONS) {
button_index = (size_t)-1;
break;
}
button_index = index;
pressed = event.motion.state & SDL_BUTTON(1);
break;
}
case SDL_MOUSEBUTTONDOWN: {
if (event.button.button == 1) {
pressed = true;
}
break;
}
case SDL_MOUSEBUTTONUP: {
if (event.button.button == 1) {
pressed = false;
if (button_index != (size_t)-1) {
(*button_handler[button_index])(window);
}
}
break;
}
}
} while (SDL_PollEvent(&event) != 0);
} while (!quit);
// destroy textures
for (size_t i = 0; i != NUM_BUTTONS; ++i) {
for (size_t j = 0; j != NUM_STATES; ++j) {
SDL_DestroyTexture(textures_normal[i][j]);
}
}
// destroy renderer
SDL_DestroyRenderer(renderer);
// destroy window
SDL_DestroyWindow(window);
// quit NFD
NFD_Quit();
// quit SDL_ttf
TTF_Quit();
// quit SDL
SDL_Quit();
return 0;
}