449 lines
18 KiB
C++
449 lines
18 KiB
C++
#include <SDL3/SDL.h>
|
|
#include <SDL3_image/SDL_image.h>
|
|
#include <SDL3_ttf/SDL_ttf.h>
|
|
#include <SDL3/SDL_main.h>
|
|
|
|
#include "Animal.h"
|
|
#include "NewDialogs/DlgMainMenu.h"
|
|
#include "Sound.h"
|
|
|
|
using namespace std;
|
|
|
|
#ifdef SDL_PLATFORM_ANDROID
|
|
#include <SDL3/SDL_system.h>
|
|
#include <jni.h>
|
|
|
|
void updateViews(int x1, int y1, int w1, int h1) {
|
|
JNIEnv* env = (JNIEnv*)SDL_GetAndroidJNIEnv();
|
|
jobject activity = (jobject)SDL_GetAndroidActivity(); // is actually the Service that is used here instead of Activity
|
|
jclass cls = env->GetObjectClass(activity);
|
|
jmethodID method_id = env->GetMethodID(cls, "updateViewsFromNative", "(IIII)V");
|
|
if (!method_id) {
|
|
SDL_Log("Method not found");
|
|
return;
|
|
}
|
|
|
|
env->CallVoidMethod(activity, method_id, x1, y1, w1, h1);
|
|
}
|
|
|
|
float raw_event_x, raw_event_y;
|
|
|
|
extern "C"
|
|
JNIEXPORT void JNICALL
|
|
Java_org_libsdl_app_SDLService_passRawEventPos__FF(JNIEnv *env, jobject obj, jfloat x, jfloat y) {
|
|
raw_event_x = x;
|
|
raw_event_y = y;
|
|
}
|
|
|
|
bool should_sleep = false;
|
|
|
|
extern "C"
|
|
JNIEXPORT void JNICALL
|
|
Java_org_libsdl_app_SDLService_setSleepState__I(JNIEnv *env, jobject obj, jint should_sleep_in) {
|
|
should_sleep = should_sleep_in;
|
|
}
|
|
|
|
#endif
|
|
|
|
class Tray {
|
|
private:
|
|
SDL_Tray *tray;
|
|
SDL_TrayMenu *menu;
|
|
SDL_TrayEntry *entry_open;
|
|
SDL_TrayEntry *entry_quit;
|
|
public:
|
|
Tray(SDL_Surface *icon, DlgMainMenu *dlg_menu) {
|
|
tray = SDL_CreateTray(icon, "Rousku");
|
|
menu = SDL_CreateTrayMenu(tray);
|
|
entry_open = SDL_InsertTrayEntryAt(menu, -1, "Avaa valikko", SDL_TRAYENTRY_BUTTON);
|
|
entry_quit = SDL_InsertTrayEntryAt(menu, -1, "Sulje Rousku", SDL_TRAYENTRY_BUTTON);
|
|
SDL_SetTrayEntryCallback(entry_open, callback_open, dlg_menu);
|
|
SDL_SetTrayEntryCallback(entry_quit, callback_quit, NULL);
|
|
}
|
|
~Tray() {
|
|
SDL_Log("destroying tray");
|
|
SDL_DestroyTray(tray);
|
|
}
|
|
static void callback_open(void *userdata, SDL_TrayEntry *invoker) {
|
|
DlgMainMenu *dlg_menu = (DlgMainMenu *) userdata;
|
|
dlg_menu->setExit(false);
|
|
}
|
|
static void callback_quit(void *userdata, SDL_TrayEntry *invoker) {
|
|
SDL_Event e;
|
|
e.type = SDL_EVENT_QUIT;
|
|
SDL_PushEvent(&e);
|
|
}
|
|
};
|
|
|
|
extern "C" /* main becomes c++ function in Andoird library if this is not used */
|
|
int main(int argc, char **argv) {
|
|
#ifdef SDL_PLATFORM_ANDROID
|
|
bool full_window_mode = 1;
|
|
bool single_window_mode = 1;
|
|
float scale_factor = 2;
|
|
#else
|
|
bool full_window_mode = 0;
|
|
bool single_window_mode = 0;
|
|
float scale_factor = 1;
|
|
#endif
|
|
float options_scale_factor = 1;
|
|
|
|
if (!SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO)) {
|
|
SDL_Log("Could not init SDL %s", SDL_GetError());
|
|
}
|
|
const char *video_driver = SDL_GetCurrentVideoDriver();
|
|
SDL_Log("Video driver is %s", video_driver);
|
|
if (video_driver && strcmp(video_driver, "wayland") == 0) {
|
|
full_window_mode = 1;
|
|
}
|
|
|
|
if(!TTF_Init()) {
|
|
SDL_Log("TTF_Init: %s", SDL_GetError());
|
|
return 1;
|
|
}
|
|
|
|
SDL_Window *sdl_window = NULL;
|
|
SDL_WindowID sdl_window_id = -1;
|
|
SDL_Renderer *sdl_renderer = NULL;
|
|
sdl_window = SDL_CreateWindow("Rousku", 10, 10, SDL_WINDOW_TRANSPARENT | SDL_WINDOW_BORDERLESS | SDL_WINDOW_UTILITY | SDL_WINDOW_ALWAYS_ON_TOP);
|
|
if (sdl_window == NULL) {
|
|
SDL_Log("Error creating window: %s", SDL_GetError());
|
|
return 1;
|
|
}
|
|
sdl_window_id = SDL_GetWindowID(sdl_window);
|
|
|
|
sdl_renderer = SDL_CreateRenderer(sdl_window, NULL);
|
|
if (sdl_renderer == NULL) {
|
|
SDL_Log("error creating renderer");
|
|
}
|
|
|
|
SDL_SetRenderScale(sdl_renderer, scale_factor, scale_factor);
|
|
|
|
SDL_Window *sdl_menu_win = NULL;
|
|
SDL_WindowID sdl_menu_win_id = -1;
|
|
SDL_Renderer *sdl_menu_renderer = NULL;
|
|
if (!single_window_mode) {
|
|
sdl_menu_win = SDL_CreateWindow("Rouskun asetukset", 10, 10, SDL_WINDOW_HIDDEN);
|
|
if (sdl_menu_win == NULL) {
|
|
SDL_Log("Error creating window: %s", SDL_GetError());
|
|
return 1;
|
|
}
|
|
sdl_menu_win_id = SDL_GetWindowID(sdl_menu_win);
|
|
|
|
sdl_menu_renderer = SDL_CreateRenderer(sdl_menu_win, NULL);
|
|
if (sdl_menu_renderer == NULL) {
|
|
SDL_Log("error creating renderer");
|
|
}
|
|
}
|
|
|
|
Sound sound;
|
|
|
|
bool press = false;
|
|
Uint64 prev_press_ticks = 0;
|
|
float mouse_win_rel_x, mouse_win_rel_y;
|
|
SDL_Rect old_menu_size = { 10, 10, 0, 0 };
|
|
DlgMainMenu *dlg_menu = new DlgMainMenu();
|
|
|
|
Animal rousku(dlg_menu->getSkin());
|
|
|
|
int num_displays;
|
|
SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
|
|
SDL_Log("Found %d display(s)", num_displays);
|
|
for (unsigned i = 0; i < num_displays; i++) {
|
|
SDL_Log("Display %d", displays[i]);
|
|
const SDL_DisplayMode *dm = SDL_GetCurrentDisplayMode(displays[i]);
|
|
if (!dm) {
|
|
SDL_Log("Couldn't get display mode: %s\n", SDL_GetError());
|
|
return 1;
|
|
}
|
|
|
|
SDL_Log("screen w %d h %d", dm->w, dm->h);
|
|
rousku.disp_pos.w = dm->w / scale_factor;
|
|
rousku.disp_pos.h = dm->h / scale_factor;
|
|
rousku.disp_pos.x = (float) dm->w / 2 / scale_factor;
|
|
rousku.disp_pos.y = (float) dm->h / 2 / scale_factor;
|
|
}
|
|
if (full_window_mode) {
|
|
#ifdef SDL_PLATFORM_ANDROID
|
|
if (!SDL_GetWindowSize(sdl_window, &rousku.disp_pos.w, &rousku.disp_pos.h)) {
|
|
SDL_Log("Couldn't get window size: %s\n", SDL_GetError());
|
|
}
|
|
rousku.disp_pos.w /= scale_factor;
|
|
rousku.disp_pos.h /= scale_factor;
|
|
rousku.disp_pos.x = (float) rousku.disp_pos.w / 2;
|
|
rousku.disp_pos.y = (float) rousku.disp_pos.h / 2;
|
|
#endif
|
|
SDL_SetWindowPosition(sdl_window, 0, 0);
|
|
SDL_SetWindowSize(sdl_window, rousku.disp_pos.w, rousku.disp_pos.h);
|
|
}
|
|
|
|
// Get MOUSE_DOWN event when clicking unfocused window
|
|
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
|
|
|
|
SDL_Surface *icon_sfe = rousku.getPreviewSurface();
|
|
Tray *tray = new Tray(icon_sfe, dlg_menu);
|
|
|
|
#ifdef SDL_PLATFORM_ANDROID
|
|
// On Android we need options to be big so that it is touch friendly.
|
|
// Also we want it to be big as touches cant go outside of view to the OS.
|
|
SDL_Surface *dlg_init_menu_sfe = dlg_menu->getSurface();
|
|
if (dlg_init_menu_sfe) {
|
|
float w_ratio = (float) rousku.disp_pos.w / dlg_init_menu_sfe->w;
|
|
float h_ratio = (float) rousku.disp_pos.h / dlg_init_menu_sfe->h;
|
|
if (w_ratio > h_ratio) {
|
|
options_scale_factor = h_ratio;
|
|
} else {
|
|
options_scale_factor = w_ratio;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
SDL_Texture *sdl_texture = NULL;
|
|
SDL_Texture *sdl_menu_texture = NULL;
|
|
|
|
while (!dlg_menu->isAppExit()) {
|
|
#ifdef SDL_PLATFORM_ANDROID
|
|
// We have been told to sleep. Maybe screen is off or something. But should not do anything...
|
|
if (should_sleep) {
|
|
SDL_Delay(1000);
|
|
continue;
|
|
}
|
|
#endif
|
|
SDL_Event event;
|
|
SDL_zero(event);
|
|
while (SDL_PollEvent(&event)) {
|
|
if (event.type == SDL_EVENT_KEY_DOWN) {
|
|
} else if (event.type == SDL_EVENT_KEY_UP) {
|
|
} else if (event.type == SDL_EVENT_QUIT) {
|
|
dlg_menu->setAppExit(true);
|
|
} else if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
|
|
bool swm_menu_pressed = false;
|
|
if (single_window_mode && !dlg_menu->isExit()) {
|
|
SDL_Surface *dlg_menu_sfe = dlg_menu->getSurface();
|
|
swm_menu_pressed = dlg_menu->onMousePress(
|
|
(event.button.x / scale_factor - ((float) rousku.disp_pos.w / 2 - (float) dlg_menu_sfe->w * options_scale_factor / 2)) / options_scale_factor,
|
|
(event.button.y / scale_factor - ((float) rousku.disp_pos.h / 2 - (float) dlg_menu_sfe->h * options_scale_factor / 2)) / options_scale_factor
|
|
);
|
|
}
|
|
if (swm_menu_pressed) {
|
|
// Menu pressed. Do not try to press Animal.
|
|
} else if (event.button.windowID == sdl_window_id) {
|
|
SDL_Log("btn down");
|
|
rousku.setSpecialState(hang);
|
|
press = true;
|
|
#ifdef SDL_PLATFORM_ANDROID
|
|
if (!full_window_mode) {
|
|
event.button.x = raw_event_x;
|
|
event.button.y = raw_event_y;
|
|
}
|
|
mouse_win_rel_x = event.button.x / scale_factor - rousku.disp_pos.x;
|
|
mouse_win_rel_y = event.button.y / scale_factor - rousku.disp_pos.y;
|
|
#else
|
|
if (full_window_mode) {
|
|
mouse_win_rel_x = event.button.x / scale_factor - rousku.disp_pos.x;
|
|
mouse_win_rel_y = event.button.y / scale_factor - rousku.disp_pos.y;
|
|
} else {
|
|
mouse_win_rel_x = event.button.x / scale_factor;
|
|
mouse_win_rel_y = event.button.y / scale_factor;
|
|
}
|
|
#endif
|
|
} else {
|
|
dlg_menu->onMousePress(event.button.x / scale_factor, event.button.y / scale_factor);
|
|
}
|
|
} else if (event.type == SDL_EVENT_MOUSE_BUTTON_UP) {
|
|
#ifdef SDL_PLATFORM_ANDROID
|
|
if (!full_window_mode) {
|
|
event.button.x = raw_event_x;
|
|
event.button.y = raw_event_y;
|
|
}
|
|
#endif
|
|
if (press) {
|
|
SDL_Log("btn up");
|
|
rousku.setSpecialState(drop);
|
|
rousku.resetTimes(SDL_GetTicks());
|
|
press = false;
|
|
|
|
Uint64 ticks = SDL_GetTicks();
|
|
|
|
// show menu if double clicking
|
|
if (ticks - prev_press_ticks < 300) {
|
|
dlg_menu->setExit(false);
|
|
}
|
|
prev_press_ticks = ticks;
|
|
} else if (!dlg_menu->isExit()) {
|
|
if (single_window_mode) {
|
|
SDL_Surface *dlg_menu_sfe = dlg_menu->getSurface();
|
|
dlg_menu->onMouseRelease(
|
|
(event.button.x / scale_factor - ((float) rousku.disp_pos.w / 2 - (float) dlg_menu_sfe->w * options_scale_factor / 2)) / options_scale_factor,
|
|
(event.button.y / scale_factor - ((float) rousku.disp_pos.h / 2 - (float) dlg_menu_sfe->h * options_scale_factor / 2)) / options_scale_factor
|
|
);
|
|
} else {
|
|
dlg_menu->onMouseRelease(event.button.x / scale_factor, event.button.y / scale_factor);
|
|
}
|
|
}
|
|
} else if (event.type == SDL_EVENT_MOUSE_MOTION) {
|
|
if (event.button.windowID == sdl_window_id) {
|
|
if (press) {
|
|
float motion_x, motion_y;
|
|
SDL_GetGlobalMouseState(&motion_x, &motion_y);
|
|
#ifdef SDL_PLATFORM_ANDROID
|
|
// If not in full window mode updating from event raw values
|
|
if (full_window_mode) {
|
|
rousku.disp_pos.x = motion_x / scale_factor - mouse_win_rel_x;
|
|
rousku.disp_pos.y = motion_y / scale_factor - mouse_win_rel_y;
|
|
}
|
|
#else
|
|
rousku.disp_pos.x = motion_x / scale_factor - mouse_win_rel_x;
|
|
rousku.disp_pos.y = motion_y / scale_factor - mouse_win_rel_y;
|
|
#endif
|
|
} else if (dlg_menu->isRunAwayFromMouse()) {
|
|
rousku.shouldRunAway(event.motion.x < (float) rousku.getImage()->w / 2 ? 1 : -1); // selecting direction to run in parameter
|
|
}
|
|
}
|
|
} else if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) {
|
|
if (!single_window_mode) {
|
|
if (event.window.windowID == sdl_menu_win_id) {
|
|
dlg_menu->setExit(true);
|
|
}
|
|
}
|
|
} else if(event.type == SDL_EVENT_WINDOW_RESIZED) {
|
|
#ifdef SDL_PLATFORM_ANDROID
|
|
// We are only interested of window size when its full screen. Otherwise we control the size of window.
|
|
if (full_window_mode) {
|
|
// TODO need to reload surfaces etc??
|
|
rousku.disp_pos.w = event.window.data1 / scale_factor;
|
|
rousku.disp_pos.h = event.window.data2 / scale_factor;
|
|
SDL_Log("window size change request to w %d h %d", rousku.disp_pos.w, rousku.disp_pos.h);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifdef SDL_PLATFORM_ANDROID
|
|
// On Android SDL filters out move events outside of view but we may have raw position updated
|
|
if (!full_window_mode && press) {
|
|
rousku.disp_pos.x = raw_event_x / scale_factor - mouse_win_rel_x;
|
|
rousku.disp_pos.y = raw_event_y / scale_factor - mouse_win_rel_y;
|
|
}
|
|
#endif
|
|
|
|
string changed_setting = dlg_menu->getChangedSetting();
|
|
if (changed_setting == "skin_list") {
|
|
rousku.readSkin(dlg_menu->getSkin());
|
|
icon_sfe = rousku.getPreviewSurface();
|
|
delete tray;
|
|
tray = new Tray(icon_sfe, dlg_menu);
|
|
rousku.setSpecialState(drop);
|
|
}
|
|
Uint64 delay = rousku.step(SDL_GetTicks(), press);
|
|
if (rousku.hasStateChanged()) {
|
|
sound.closeSound(); // so it will be reloaded with right format
|
|
}
|
|
SDL_Surface *animal_sfe = rousku.getImage();
|
|
|
|
#ifdef SDL_PLATFORM_ANDROID
|
|
// On android we need full window mode when we show options and animal at the same time.
|
|
// When options is not shown we scale view only to animal so that user can make
|
|
// touch events to the OS.
|
|
full_window_mode = !dlg_menu->isExit();
|
|
#else
|
|
if (!full_window_mode) {
|
|
SDL_SetWindowShape(sdl_window, animal_sfe);
|
|
}
|
|
#endif
|
|
|
|
if (!sdl_texture || sdl_texture->format != animal_sfe->format || sdl_texture->w != animal_sfe->w || sdl_texture->h != animal_sfe->h) {
|
|
if (sdl_texture) {
|
|
SDL_DestroyTexture(sdl_texture);
|
|
}
|
|
sdl_texture = SDL_CreateTexture(sdl_renderer, animal_sfe->format, SDL_TEXTUREACCESS_STREAMING, animal_sfe->w, animal_sfe->h);
|
|
if (!sdl_texture) {
|
|
SDL_Log("Couldn't create texture: %s\n", SDL_GetError());
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (!full_window_mode) {
|
|
SDL_SetWindowPosition(sdl_window, rousku.disp_pos.x, rousku.disp_pos.y);
|
|
SDL_SetWindowSize(sdl_window, animal_sfe->w, animal_sfe->h);
|
|
}
|
|
|
|
SDL_UpdateTexture(sdl_texture, NULL, animal_sfe->pixels, animal_sfe->pitch);
|
|
SDL_RenderClear(sdl_renderer);
|
|
if (full_window_mode) {
|
|
SDL_FRect pos = { (float) rousku.disp_pos.x, (float) rousku.disp_pos.y, (float) animal_sfe->w, (float) animal_sfe->h };
|
|
SDL_RenderTexture(sdl_renderer, sdl_texture, NULL, &pos);
|
|
} else {
|
|
SDL_RenderTexture(sdl_renderer, sdl_texture, NULL, NULL);
|
|
}
|
|
|
|
if (dlg_menu->isExit()) {
|
|
SDL_HideWindow(sdl_menu_win);
|
|
#ifdef SDL_PLATFORM_ANDROID
|
|
// When menu is closed view is scaled to be size of animal and set position of animal on screen.
|
|
updateViews(rousku.disp_pos.x * scale_factor, rousku.disp_pos.y * scale_factor, animal_sfe->w * scale_factor, animal_sfe->h * scale_factor);
|
|
#endif
|
|
} else {
|
|
dlg_menu->setPreviewSurface(rousku.getPreviewSurface());
|
|
|
|
SDL_Surface *dlg_menu_sfe = dlg_menu->getSurface();
|
|
if (!sdl_menu_texture || sdl_menu_texture->format != dlg_menu_sfe->format || sdl_menu_texture->w != dlg_menu_sfe->w || sdl_menu_texture->h != dlg_menu_sfe->h) {
|
|
if (sdl_menu_texture) {
|
|
SDL_DestroyTexture(sdl_menu_texture);
|
|
}
|
|
sdl_menu_texture = SDL_CreateTexture(single_window_mode ? sdl_renderer : sdl_menu_renderer, dlg_menu_sfe->format, SDL_TEXTUREACCESS_STREAMING, dlg_menu_sfe->w, dlg_menu_sfe->h);
|
|
if (!sdl_menu_texture) {
|
|
SDL_Log("Couldn't create texture: %s\n", SDL_GetError());
|
|
return 1;
|
|
}
|
|
}
|
|
if (dlg_menu_sfe->w != old_menu_size.w || dlg_menu_sfe->h != old_menu_size.h) {
|
|
if (!single_window_mode) {
|
|
SDL_SetWindowSize(sdl_menu_win, dlg_menu_sfe->w, dlg_menu_sfe->h);
|
|
}
|
|
old_menu_size.w = dlg_menu_sfe->w;
|
|
old_menu_size.h = dlg_menu_sfe->h;
|
|
}
|
|
|
|
SDL_UpdateTexture(sdl_menu_texture, NULL, dlg_menu_sfe->pixels, dlg_menu_sfe->pitch);
|
|
if (single_window_mode) {
|
|
SDL_FRect menu_pos = { (float) rousku.disp_pos.w / 2 - (float) dlg_menu_sfe->w * options_scale_factor / 2, (float) rousku.disp_pos.h / 2 - (float) dlg_menu_sfe->h * options_scale_factor / 2, (float) dlg_menu_sfe->w * options_scale_factor, (float) dlg_menu_sfe->h * options_scale_factor };
|
|
SDL_RenderTexture(sdl_renderer, sdl_menu_texture, NULL, &menu_pos);
|
|
} else {
|
|
SDL_RenderClear(sdl_menu_renderer);
|
|
SDL_RenderTexture(sdl_menu_renderer, sdl_menu_texture, NULL, NULL);
|
|
SDL_RenderPresent(sdl_menu_renderer);
|
|
}
|
|
|
|
#ifdef SDL_PLATFORM_ANDROID
|
|
// When menu is open we set view to be full screen
|
|
updateViews(0, 0, -1, -1);
|
|
#endif
|
|
|
|
SDL_ShowWindow(sdl_menu_win);
|
|
}
|
|
SDL_RenderPresent(sdl_renderer);
|
|
|
|
if (dlg_menu->isEnableSound()) {
|
|
SoundData *sd = rousku.getSound();
|
|
sound.setSound(sd);
|
|
} else {
|
|
sound.closeSound();
|
|
}
|
|
|
|
SDL_SetWindowAlwaysOnTop(sdl_window, dlg_menu->isAlwaysOnTop()); // FIXME on false seems to stay behind tint2, but otherwis still stays on top of normal windows, maybe because of UTILITY-window?
|
|
|
|
SDL_Delay(delay);
|
|
}
|
|
|
|
delete tray;
|
|
|
|
SDL_DestroyTexture(sdl_menu_texture);
|
|
SDL_DestroyTexture(sdl_texture);
|
|
SDL_DestroyWindow(sdl_window);
|
|
SDL_Quit();
|
|
TTF_Quit();
|
|
|
|
return 0;
|
|
}
|