Files
SDLRousku/main.cpp
2025-09-23 00:27:56 +03:00

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;
}