#include #include #include #include #include "Animal.h" #include "NewDialogs/DlgMainMenu.h" #include "Sound.h" using namespace std; #ifdef SDL_PLATFORM_ANDROID #include #include 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; }