From 1b01db297b73f1f32b47bae21fcc7dd3f422ff22 Mon Sep 17 00:00:00 2001 From: bubio Date: Sat, 1 Aug 2026 16:23:15 +0900 Subject: [PATCH 1/2] Add Android portrait game controls layout --- .../Android/app/src/main/AndroidManifest.xml | 2 +- .../app/src/main/java/net/retropc/pi/XM8.java | 13 +- Source/UI/app.h | 12 +- Source/UI/input.cpp | 42 +++--- Source/UI/main.cpp | 3 +- Source/UI/video.cpp | 125 ++++++++++++++---- Source/UI/video.h | 24 ++-- 7 files changed, 154 insertions(+), 67 deletions(-) diff --git a/Builder/Android/app/src/main/AndroidManifest.xml b/Builder/Android/app/src/main/AndroidManifest.xml index 26fd249..671b0a1 100644 --- a/Builder/Android/app/src/main/AndroidManifest.xml +++ b/Builder/Android/app/src/main/AndroidManifest.xml @@ -75,7 +75,7 @@ android:launchMode="singleInstance" android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation" android:preferMinimalPostProcessing="true" - android:screenOrientation="sensorLandscape" + android:screenOrientation="sensor" android:exported="true"> diff --git a/Builder/Android/app/src/main/java/net/retropc/pi/XM8.java b/Builder/Android/app/src/main/java/net/retropc/pi/XM8.java index 353f6dc..c35d67f 100644 --- a/Builder/Android/app/src/main/java/net/retropc/pi/XM8.java +++ b/Builder/Android/app/src/main/java/net/retropc/pi/XM8.java @@ -51,7 +51,8 @@ import android.view.KeyEvent; import android.view.WindowManager; import androidx.core.content.ContextCompat; -import android.content.pm.PackageManager; +import android.content.pm.PackageManager; +import android.content.pm.ActivityInfo; import androidx.core.app.ActivityCompat; import android.view.WindowInsets; import android.view.WindowInsetsController; @@ -69,7 +70,15 @@ public class XM8 extends SDLActivity { // log - private static final String LOG_TAG = "XM8"; + private static final String LOG_TAG = "XM8"; + + @Override + public void setOrientationBis(int w, int h, boolean resizable, String hint) { + // XM8 supports both the legacy landscape layout and the portrait + // game/control split. SDL's default picks landscape from its initial + // 640x400 window size, so keep the Activity responsive to user rotation. + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER); + } // directory and filename private static final String ROM_DIRECTORY = "/XM8/"; diff --git a/Source/UI/app.h b/Source/UI/app.h index c3b48b8..44ae6a5 100644 --- a/Source/UI/app.h +++ b/Source/UI/app.h @@ -230,10 +230,12 @@ class App // get version const char* GetAppTitle(); // get application title - void* GetEvMgr(); - // get event manager - -private: + void* GetEvMgr(); + // get event manager + bool IsRaOverlayBlocking() const; + // check blocking RA overlay + +private: // drawing void Draw(); // rendering @@ -408,8 +410,6 @@ class App // save legacy XM8 state body void ChangeSystemInternal(bool load, bool preserve_ra_session); // rebuild VM with explicit RA lifecycle policy - bool IsRaOverlayBlocking() const; - // check blocking RA overlay bool OpenStartupDisks(const std::vector& disks, std::string *error); // open CLI disks bool OpenDroppedDisk(const char *path, std::string *error); diff --git a/Source/UI/input.cpp b/Source/UI/input.cpp index 4c2c6b8..e8bfa59 100644 --- a/Source/UI/input.cpp +++ b/Source/UI/input.cpp @@ -907,7 +907,7 @@ void Input::OnMouseMotion(SDL_Event *e) // convert point x = e->motion.x; y = e->motion.y; - if (video->ConvertPoint(&x, &y) == false) { + if (video->ConvertSoftKeyPoint(&x, &y) == false) { return; } @@ -948,7 +948,7 @@ void Input::OnMouseButtonDown(SDL_Event *e) // convert point x = e->button.x; y = e->button.y; - if (video->ConvertPoint(&x, &y) == false) { + if (video->ConvertSoftKeyPoint(&x, &y) == false) { return; } @@ -990,7 +990,7 @@ void Input::OnMouseButtonUp(SDL_Event *e) // convert point x = e->button.x; y = e->button.y; - if (video->ConvertPoint(&x, &y) == false) { + if (video->ConvertSoftKeyPoint(&x, &y) == false) { return; } @@ -1108,11 +1108,11 @@ void Input::OnFingerDown(SDL_Event *e) SDL_assert(e->tfinger.fingerId >= 0); // convert finger - x = 0; - y = 0; - if (video->ConvertFinger(e->tfinger.x, e->tfinger.y, &x, &y) == false) { - return; - } + x = 0; + y = 0; + if (video->ConvertSoftKeyFinger(e->tfinger.x, e->tfinger.y, &x, &y) == false) { + return; + } OnInputCommon(x, y, true, (int)e->tfinger.fingerId); } @@ -1121,7 +1121,7 @@ void Input::OnFingerDown(SDL_Event *e) // OnFingerUp() // finger up // -void Input::OnFingerUp(SDL_Event *e) +void Input::OnFingerUp(SDL_Event *e) { int x; int y; @@ -1129,11 +1129,12 @@ void Input::OnFingerUp(SDL_Event *e) SDL_assert(e->tfinger.fingerId >= 0); // convert finger - x = 0; - y = 0; - if (video->ConvertFinger(e->tfinger.x, e->tfinger.y, &x, &y) == false) { - return; - } + x = 0; + y = 0; + if (video->ConvertSoftKeyFinger(e->tfinger.x, e->tfinger.y, &x, &y) == false) { + OnInputMove(NULL, (int)e->tfinger.fingerId); + return; + } OnInputCommon(x, y, false, (int)e->tfinger.fingerId); } @@ -1142,7 +1143,7 @@ void Input::OnFingerUp(SDL_Event *e) // OnFingerMotion() // finger motion // -void Input::OnFingerMotion(SDL_Event *e) +void Input::OnFingerMotion(SDL_Event *e) { int x; int y; @@ -1150,11 +1151,12 @@ void Input::OnFingerMotion(SDL_Event *e) SDL_assert(e->tfinger.fingerId >= 0); // convert finger - x = 0; - y = 0; - if (video->ConvertFinger(e->tfinger.x, e->tfinger.y, &x, &y) == false) { - return; - } + x = 0; + y = 0; + if (video->ConvertSoftKeyFinger(e->tfinger.x, e->tfinger.y, &x, &y) == false) { + OnInputMove(NULL, (int)e->tfinger.fingerId); + return; + } OnInputCommon(x, y, true, (int)e->tfinger.fingerId); } diff --git a/Source/UI/main.cpp b/Source/UI/main.cpp index bcfb1e9..ee9a910 100644 --- a/Source/UI/main.cpp +++ b/Source/UI/main.cpp @@ -81,7 +81,8 @@ int main(int argc, char *argv[]) #if SDL_VERSION_ATLEAST(2, 0, 12) SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); #endif - SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight"); + SDL_SetHint(SDL_HINT_ORIENTATIONS, + "LandscapeLeft LandscapeRight Portrait PortraitUpsideDown"); // initialize SDL ret = SDL_Init( diff --git a/Source/UI/video.cpp b/Source/UI/video.cpp index c343d95..d185dbd 100644 --- a/Source/UI/video.cpp +++ b/Source/UI/video.cpp @@ -84,8 +84,9 @@ Video::Video(App *a) softkey_buf = NULL; // parameter - horizontal = false; - menu_mode = false; + horizontal = false; + portrait_split = false; + menu_mode = false; window_width = 0; window_height = 0; brightness = 0; @@ -105,8 +106,9 @@ Video::Video(App *a) // rect SDL_zero(draw_rect); SDL_zero(src_rect); - SDL_zero(status_rect); - memset(clear_rect, 0, sizeof(clear_rect)); + SDL_zero(status_rect); + SDL_zero(softkey_rect); + memset(clear_rect, 0, sizeof(clear_rect)); // drive status for (loop=0; loop width; +#endif + draw_height = portrait_split ? height / 2 : height; + if (portrait_split) { + softkey_rect.x = 0; + softkey_rect.y = draw_height; + softkey_rect.w = width; + softkey_rect.h = height - draw_height; + softkey_mod = 0; + if (softkey_texture != NULL) { + SDL_SetTextureAlphaMod(softkey_texture, 0xff); + } + } + else { + SDL_zero(softkey_rect); + } // update video_height status = setting->HasStatusLine(); @@ -376,7 +403,7 @@ void Video::SetWindowSize(int width, int height) } // check aspect - if ((height * SCREEN_WIDTH) >= (width * video_height)) { + if ((draw_height * SCREEN_WIDTH) >= (width * video_height)) { horizontal = false; } else { @@ -388,8 +415,8 @@ void Video::SetWindowSize(int width, int height) draw_rect.x = 0; draw_rect.y = 0; draw_rect.w = width; - draw_rect.h = (width * video_height) / SCREEN_WIDTH; - if (draw_rect.h == height) { + draw_rect.h = (width * video_height) / SCREEN_WIDTH; + if (draw_rect.h == draw_height) { // just window rect = draw rect clear_rect[0].w = 0; clear_rect[0].h = 0; @@ -398,7 +425,7 @@ void Video::SetWindowSize(int width, int height) } else { // centering - draw_rect.y = (height / 2) - (draw_rect.h / 2); + draw_rect.y = (draw_height / 2) - (draw_rect.h / 2); clear_rect[0].x = 0; clear_rect[0].y = 0; clear_rect[0].w = width; @@ -406,15 +433,15 @@ void Video::SetWindowSize(int width, int height) clear_rect[1].x = 0; clear_rect[1].y = draw_rect.y + draw_rect.h; clear_rect[1].w = width; - clear_rect[1].h = height - clear_rect[1].y; + clear_rect[1].h = draw_height - clear_rect[1].y; } } else { // horizontal (landscape) draw_rect.x = 0; draw_rect.y = 0; - draw_rect.w = (height * SCREEN_WIDTH) / video_height; - draw_rect.h = height; + draw_rect.w = (draw_height * SCREEN_WIDTH) / video_height; + draw_rect.h = draw_height; if (draw_rect.w == width) { // just window rect = draw rect clear_rect[0].w = 0; @@ -428,11 +455,11 @@ void Video::SetWindowSize(int width, int height) clear_rect[0].x = 0; clear_rect[0].y = 0; clear_rect[0].w = draw_rect.x; - clear_rect[0].h = height; + clear_rect[0].h = draw_height; clear_rect[1].x = draw_rect.x + draw_rect.w; clear_rect[1].y = 0; clear_rect[1].w = width - clear_rect[1].x; - clear_rect[1].h = height; + clear_rect[1].h = draw_height; } } @@ -661,7 +688,7 @@ bool Video::ConvertPoint(int *x, int *y) // ConvertFinger() // convert finger point from window to texture // -bool Video::ConvertFinger(float tx, float ty, int *x, int *y) +bool Video::ConvertFinger(float tx, float ty, int *x, int *y) { // multiple with window rect tx *= (float)window_width; @@ -672,8 +699,43 @@ bool Video::ConvertFinger(float tx, float ty, int *x, int *y) *y = (int)ty; // convert point - return ConvertPoint(x, y); -} + return ConvertPoint(x, y); +} + +// +// ConvertSoftKeyPoint() +// convert point from window to softkey texture +// +bool Video::ConvertSoftKeyPoint(int *x, int *y) +{ + if (portrait_split == false) { + return ConvertPoint(x, y); + } + + int draw_x = *x - softkey_rect.x; + int draw_y = *y - softkey_rect.y; + if (draw_x < 0 || draw_x >= softkey_rect.w || + draw_y < 0 || draw_y >= softkey_rect.h) { + *x = 0; + *y = 0; + return false; + } + + *x = (draw_x * SCREEN_WIDTH) / softkey_rect.w; + *y = (draw_y * SCREEN_HEIGHT) / softkey_rect.h; + return true; +} + +// +// ConvertSoftKeyFinger() +// convert finger point from window to softkey texture +// +bool Video::ConvertSoftKeyFinger(float tx, float ty, int *x, int *y) +{ + *x = (int)(tx * (float)window_width); + *y = (int)(ty * (float)window_height); + return ConvertSoftKeyPoint(x, y); +} // // Draw() @@ -778,7 +840,7 @@ void Video::Draw() } // clear if required - if ((clear_rect[0].w != 0) || (clear_rect[0].h != 0)) { + if (portrait_split || (clear_rect[0].w != 0) || (clear_rect[0].h != 0)) { SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderClear(renderer); } @@ -790,7 +852,12 @@ void Video::Draw() ret = SDL_RenderCopy(renderer, status_texture, NULL, &status_rect); } - if (softkey_mode == true) { + if (portrait_split == true && app->IsRaOverlayBlocking() == false) { + if (ret == 0) { + ret = SDL_RenderCopy(renderer, softkey_texture, NULL, &softkey_rect); + } + } + else if (softkey_mode == true) { // softkey = enable if (ret == 0) { ret = SDL_RenderCopy(renderer, softkey_texture, NULL, &draw_rect); @@ -1207,7 +1274,7 @@ void Video::DrawMenu(bool status) CopyFrameBuf(menu_texture, menu_buf, SCREEN_HEIGHT); // clear if required - if ((clear_rect[0].w != 0) || (clear_rect[0].h != 0)) { + if (portrait_split || (clear_rect[0].w != 0) || (clear_rect[0].h != 0)) { SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderClear(renderer); } diff --git a/Source/UI/video.h b/Source/UI/video.h index ee7721f..175eac9 100644 --- a/Source/UI/video.h +++ b/Source/UI/video.h @@ -65,10 +65,14 @@ class Video // enable displaying softkey void UpdateSoftKey(); // update softkey texture from frame buffer - bool ConvertPoint(int *x, int *y); - // convert point from window to texturure - bool ConvertFinger(float tx, float ty, int *x, int *y); - // convert finger point from window to texture + bool ConvertPoint(int *x, int *y); + // convert point from window to texturure + bool ConvertFinger(float tx, float ty, int *x, int *y); + // convert finger point from window to texture + bool ConvertSoftKeyPoint(int *x, int *y); + // convert point from window to softkey texture + bool ConvertSoftKeyFinger(float tx, float ty, int *x, int *y); + // convert finger point from window to softkey texture // main scheduler void SetFrameRate(Uint32 rate); @@ -137,8 +141,10 @@ class Video // frame buffer for softkey // flag and parameter - bool horizontal; - // screen direction + bool horizontal; + // screen direction + bool portrait_split; + // Android portrait game/control split bool menu_mode; // menu mode int window_width; @@ -149,8 +155,10 @@ class Video // drawing rect SDL_Rect src_rect; // source rect - SDL_Rect status_rect; - // status rect + SDL_Rect status_rect; + // status rect + SDL_Rect softkey_rect; + // dedicated softkey rect in portrait mode SDL_Rect clear_rect[2]; // clear rect Uint8 brightness; From 0e4f8889d1e626dc349401525f61bf42436a42e0 Mon Sep 17 00:00:00 2001 From: bubio Date: Mon, 3 Aug 2026 08:20:10 +0900 Subject: [PATCH 2/2] Add Android rotation mode and safe-area handling --- .github/workflows/Android.yml | 2 +- Builder/Android/app/jni/src/saf.c | 14 ++ Builder/Android/app/jni/src/xm8jni.h | 4 + .../Android/app/src/main/AndroidManifest.xml | 1 + .../app/src/main/java/net/retropc/pi/XM8.java | 106 +++++++++++++- Source/UI/app.cpp | 19 ++- Source/UI/app.h | 8 +- Source/UI/menu.cpp | 60 ++++++-- Source/UI/menuid.h | 14 +- Source/UI/setting.cpp | 65 +++++++-- Source/UI/setting.h | 25 +++- Source/UI/video.cpp | 130 ++++++++++++------ Source/UI/video.h | 2 + 13 files changed, 358 insertions(+), 92 deletions(-) diff --git a/.github/workflows/Android.yml b/.github/workflows/Android.yml index 0fa567c..e219a12 100644 --- a/.github/workflows/Android.yml +++ b/.github/workflows/Android.yml @@ -50,7 +50,7 @@ jobs: path: | Builder/Android/app/jni/SDL Builder/Android/app/src/main/java/org/libsdl - key: ${{ runner.os }}-android-sdl2-${{ hashFiles('Builder/Android/setup_sdl2.sh') }} + key: ${{ runner.os }}-android-sdl2-${{ hashFiles('Builder/Android/setup_sdl2.sh', 'Builder/Android/patches/sdl2-2.32.10-android-compat.patch') }} restore-keys: | ${{ runner.os }}-android-sdl2- diff --git a/Builder/Android/app/jni/src/saf.c b/Builder/Android/app/jni/src/saf.c index 594cd2b..7a1d178 100644 --- a/Builder/Android/app/jni/src/saf.c +++ b/Builder/Android/app/jni/src/saf.c @@ -575,6 +575,20 @@ static jmethodID JNI_RaMethod(JNIEnv *env, const char *name, const char *signatu return (*env)->GetMethodID(env, java_class, name, signature); } +// +// Android_SetRotationMode() +// apply Android Activity rotation mode +// +void Android_SetRotationMode(int mode) +{ + JNIEnv *env = JNI_GetEnvironment(); + if (env == NULL || java_activity == NULL || java_class == NULL) return; + jmethodID id = JNI_RaMethod(env, "setRotationMode", "(I)V"); + if (id == NULL) return; + (*env)->CallVoidMethod(env, java_activity, id, (jint)mode); + JNI_HasException(env); +} + void Android_RaShowLogin(const char *username) { JNIEnv *env = JNI_GetEnvironment(); diff --git a/Builder/Android/app/jni/src/xm8jni.h b/Builder/Android/app/jni/src/xm8jni.h index e42aee2..29aa302 100644 --- a/Builder/Android/app/jni/src/xm8jni.h +++ b/Builder/Android/app/jni/src/xm8jni.h @@ -104,6 +104,10 @@ int Android_ChDir(char *dir, const char *name); // void Android_PollJoystick(void); +// Android_SetRotationMode() +// apply auto, landscape, or portrait Activity rotation mode +void Android_SetRotationMode(int mode); + // RetroAchievements Android bridge (available only in RA-enabled builds). int Android_RaHttpSend(unsigned long long request_id, const char *url, const char *post_data, const char *content_type, int connect_timeout_ms, diff --git a/Builder/Android/app/src/main/AndroidManifest.xml b/Builder/Android/app/src/main/AndroidManifest.xml index 671b0a1..67c62b7 100644 --- a/Builder/Android/app/src/main/AndroidManifest.xml +++ b/Builder/Android/app/src/main/AndroidManifest.xml @@ -55,6 +55,7 @@ An example Java class can be found in README-android.md --> = 30) { + Insets safe = insets.getInsetsIgnoringVisibility( + WindowInsets.Type.displayCutout()); + top = safe.top; + bottom = safe.bottom; + } else { + top = 0; + bottom = 0; + if (Build.VERSION.SDK_INT >= 28) { + DisplayCutout cutout = insets.getDisplayCutout(); + if (cutout != null) { + top = Math.max(top, cutout.getSafeInsetTop()); + bottom = Math.max(bottom, cutout.getSafeInsetBottom()); + } + } + } + + if (Build.VERSION.SDK_INT >= 31) { + RoundedCorner topLeft = insets.getRoundedCorner( + RoundedCorner.POSITION_TOP_LEFT); + RoundedCorner topRight = insets.getRoundedCorner( + RoundedCorner.POSITION_TOP_RIGHT); + RoundedCorner bottomLeft = insets.getRoundedCorner( + RoundedCorner.POSITION_BOTTOM_LEFT); + RoundedCorner bottomRight = insets.getRoundedCorner( + RoundedCorner.POSITION_BOTTOM_RIGHT); + top = Math.max(top, Math.max(radiusOf(topLeft), radiusOf(topRight))); + bottom = Math.max(bottom, Math.max(radiusOf(bottomLeft), radiusOf(bottomRight))); + } + + view.setPadding(0, top, 0, bottom); + return insets; + } + }); + mLayout.requestApplyInsets(); + } + + private static int radiusOf(RoundedCorner corner) { + return corner == null ? 0 : corner.getRadius(); + } @Override public void setOrientationBis(int w, int h, boolean resizable, String hint) { // XM8 supports both the legacy landscape layout and the portrait // game/control split. SDL's default picks landscape from its initial // 640x400 window size, so keep the Activity responsive to user rotation. - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER); + applyRotationMode(); + } + + public void setRotationMode(final int mode) { + mRotationMode = (mode == ROTATION_LANDSCAPE || mode == ROTATION_PORTRAIT) + ? mode : ROTATION_AUTO; + runOnUiThread(new Runnable() { + @Override public void run() { + if (!isFinishing()) applyRotationMode(); + } + }); + } + + private void applyRotationMode() { + switch (mRotationMode) { + case ROTATION_LANDSCAPE: + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + break; + case ROTATION_PORTRAIT: + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); + break; + default: + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER); + break; + } } // directory and filename @@ -180,8 +271,9 @@ protected void onCreate(Bundle savedInstanceState) { // super class super.onCreate(savedInstanceState); - // immersive full-screen mode or dim status bar / navigation icon - setupWindow(); + // immersive full-screen mode or dim status bar / navigation icon + setupWindow(); + installSafeAreaInsets(); // set Build.VERSION.SDK_INT and app files directory nativeBuildVer(Build.VERSION.SDK_INT); diff --git a/Source/UI/app.cpp b/Source/UI/app.cpp index e348b70..5f197ed 100644 --- a/Source/UI/app.cpp +++ b/Source/UI/app.cpp @@ -44,6 +44,9 @@ #include "tapemgr.h" #include "clidisk.h" #include "m3u.h" +#ifdef __ANDROID__ +#include "xm8jni.h" +#endif #ifdef XM8_ENABLE_RETROACHIEVEMENTS #include "ra_media_change_policy.h" #include "ra_build_info.h" @@ -616,6 +619,9 @@ bool App::Init(const CliOptions& options) Deinit(); return false; } +#ifdef __ANDROID__ + ApplyAndroidRotationMode(); +#endif if (ApplyCommandLineSettings(options) == false) { Deinit(); return false; @@ -5875,7 +5881,7 @@ bool App::IsFullSpeed() // SetWindowWidth() // set window width // -void App::SetWindowWidth() +void App::SetWindowWidth() { int width; int height; @@ -5979,6 +5985,17 @@ void App::CtrlAudio() audio->Stop(); } } + +#ifdef __ANDROID__ +// +// ApplyAndroidRotationMode() +// apply persisted Android Activity rotation mode +// +void App::ApplyAndroidRotationMode() +{ + Android_SetRotationMode(setting->GetRotationMode()); +} +#endif // // ChangeAudio() diff --git a/Source/UI/app.h b/Source/UI/app.h index 44ae6a5..536ba19 100644 --- a/Source/UI/app.h +++ b/Source/UI/app.h @@ -119,8 +119,12 @@ class App // normal speed bool IsFullSpeed(); // get full speed flag - void SetWindowWidth(); - // set window width + void SetWindowWidth(); + // set window width +#ifdef __ANDROID__ + void ApplyAndroidRotationMode(); + // apply Android rotation mode +#endif // action void OnKeyVM(SDL_Scancode code); diff --git a/Source/UI/menu.cpp b/Source/UI/menu.cpp index e2f6b97..3b69c88 100644 --- a/Source/UI/menu.cpp +++ b/Source/UI/menu.cpp @@ -996,10 +996,15 @@ void Menu::EnterVideo(int id) // scaling filter list->AddButton("Scaling filter", MENU_VIDEO_SCALEFILTER); -#ifdef __ANDROID__ - // force RGB565 - list->AddCheckButton("RGB565 (for Samsung Galaxy)", MENU_VIDEO_FORCERGB565); -#endif // __ANDROID__ +#ifdef __ANDROID__ + // force RGB565 + list->AddCheckButton("RGB565 (for Samsung Galaxy)", MENU_VIDEO_FORCERGB565); + + // rotation mode + list->AddRadioButton("Rotation: Auto", MENU_VIDEO_ROTATE_AUTO, MENU_VIDEO_ROTATION); + list->AddRadioButton("Rotation: Landscape", MENU_VIDEO_ROTATE_LANDSCAPE, MENU_VIDEO_ROTATION); + list->AddRadioButton("Rotation: Portrait", MENU_VIDEO_ROTATE_PORTRAIT, MENU_VIDEO_ROTATION); +#endif // __ANDROID__ // skip frame switch (setting->GetSkipFrame()) { @@ -1060,10 +1065,23 @@ void Menu::EnterVideo(int id) list->SetCheck(MENU_VIDEO_STATUSCHK, setting->HasStatusLine()); list->SetSlider(MENU_VIDEO_STATUSALPHA, setting->GetStatusAlpha()); -#ifdef __ANDROID__ - // force RGB565 - list->SetCheck(MENU_VIDEO_FORCERGB565, setting->IsForceRGB565()); -#endif // __ANDROID__ +#ifdef __ANDROID__ + // force RGB565 + list->SetCheck(MENU_VIDEO_FORCERGB565, setting->IsForceRGB565()); + + // rotation mode + switch (setting->GetRotationMode()) { + case Setting::RotationLandscape: + list->SetRadio(MENU_VIDEO_ROTATE_LANDSCAPE, MENU_VIDEO_ROTATION); + break; + case Setting::RotationPortrait: + list->SetRadio(MENU_VIDEO_ROTATE_PORTRAIT, MENU_VIDEO_ROTATION); + break; + default: + list->SetRadio(MENU_VIDEO_ROTATE_AUTO, MENU_VIDEO_ROTATION); + break; + } +#endif // __ANDROID__ // set focus list->SetFocus(id); @@ -1241,7 +1259,7 @@ void Menu::EnterInput(int id) list->AddSlider("Softkey transparency", MENU_INPUT_SOFTALPHA, 0, 0xff, 1); list->AddSlider("Softkey timeout", MENU_INPUT_SOFTTIME, 400, 20000, 200); list->AddCheckButton("Show Softkey Mouse Move", MENU_INPUT_SOFTKEY_MM); -#ifdef __ANDROID__ +#ifdef __ANDROID__ list->AddCheckButton("Keyboard enable", MENU_INPUT_KEYENABLE); #endif // __ANDROID__ list->AddCheckButton("Joystick enable", MENU_INPUT_JOYENABLE); @@ -2831,9 +2849,27 @@ void Menu::CmdVideo(bool down, int id) setting->SetForceRGB565(true); list->SetCheck(MENU_VIDEO_FORCERGB565, true); } - } - break; -#endif // __ANDROID__ + } + break; + + // rotation mode + case MENU_VIDEO_ROTATE_AUTO: + case MENU_VIDEO_ROTATE_LANDSCAPE: + case MENU_VIDEO_ROTATE_PORTRAIT: + if (down == false) { + int rotation = Setting::RotationAuto; + if (id == MENU_VIDEO_ROTATE_LANDSCAPE) { + rotation = Setting::RotationLandscape; + } + else if (id == MENU_VIDEO_ROTATE_PORTRAIT) { + rotation = Setting::RotationPortrait; + } + setting->SetRotationMode(rotation); + list->SetRadio(id, MENU_VIDEO_ROTATION); + app->ApplyAndroidRotationMode(); + } + break; +#endif // __ANDROID__ default: break; diff --git a/Source/UI/menuid.h b/Source/UI/menuid.h index b7807b1..1f062b5 100644 --- a/Source/UI/menuid.h +++ b/Source/UI/menuid.h @@ -296,9 +296,17 @@ // status alpha #define MENU_VIDEO_SCALEFILTER 816 // scaling filter -#define MENU_VIDEO_FORCERGB565 817 - // force RGB565 -#define MENU_VIDEO_WINDOW 896 +#define MENU_VIDEO_FORCERGB565 817 + // force RGB565 +#define MENU_VIDEO_ROTATE_AUTO 818 + // auto rotation +#define MENU_VIDEO_ROTATE_LANDSCAPE 819 + // landscape rotation +#define MENU_VIDEO_ROTATE_PORTRAIT 820 + // portrait rotation +#define MENU_VIDEO_ROTATION 895 + // rotation radio +#define MENU_VIDEO_WINDOW 896 // window radio #define MENU_VIDEO_SKIP 897 // skip radio diff --git a/Source/UI/setting.cpp b/Source/UI/setting.cpp index 74593f4..f979816 100644 --- a/Source/UI/setting.cpp +++ b/Source/UI/setting.cpp @@ -58,8 +58,10 @@ // version 1.70 #define SETTING_VERSION_171 20230401 // version 1.71 -#define SETTING_VERSION_173 20230501 - // version 1.73 +#define SETTING_VERSION_173 20230501 + // version 1.73 +#define SETTING_VERSION_200 20260801 + // version 2.00 // video #define DEFAULT_WINDOW_WIDTH 640 @@ -149,7 +151,8 @@ Setting::Setting() status_alpha = DEFAULT_STATUS_ALPHA; scale_quality[0] = (char)('0' + DEFAULT_SCALE_QUALITY); scale_quality[1] = '\0'; - force_rgb565 = DEFAULT_FORCE_RGB565; + force_rgb565 = DEFAULT_FORCE_RGB565; + rotation_mode = RotationAuto; // setting (input) softkey_index = 0; @@ -378,10 +381,18 @@ bool Setting::LoadSetting(FILEIO *fio) } // version 1.73 - if (version >= SETTING_VERSION_173) { - cursor_to_numpad_enable = fio->FgetBool(); - num_to_numpad_enable = fio->FgetBool(); - } + if (version >= SETTING_VERSION_173) { + cursor_to_numpad_enable = fio->FgetBool(); + num_to_numpad_enable = fio->FgetBool(); + } + + // version 2.00 + if (version >= SETTING_VERSION_200) { + rotation_mode = fio->FgetInt32(); + if (rotation_mode < RotationAuto || rotation_mode > RotationPortrait) { + rotation_mode = RotationAuto; + } + } return true; } @@ -416,7 +427,7 @@ void Setting::SaveSetting(FILEIO *fio) int loop; // version - fio->FputUint32(SETTING_VERSION_173); + fio->FputUint32(SETTING_VERSION_200); // system fio->FputInt32(config.boot_mode); @@ -475,9 +486,12 @@ void Setting::SaveSetting(FILEIO *fio) fio->FputBool(mouse_move_softkey_enable); // version 1.73 - fio->FputBool(cursor_to_numpad_enable); - fio->FputBool(num_to_numpad_enable); -} + fio->FputBool(cursor_to_numpad_enable); + fio->FputBool(num_to_numpad_enable); + + // version 2.00 + fio->FputInt32(rotation_mode); +} // // GetSettingDir() @@ -664,10 +678,31 @@ int Setting::GetWindowWidth() // SetWindowWidth() // set window width // -void Setting::SetWindowWidth(int width) -{ - window_width = width; -} +void Setting::SetWindowWidth(int width) +{ + window_width = width; +} + +// +// GetRotationMode() +// get Android rotation mode +// +int Setting::GetRotationMode() +{ + return rotation_mode; +} + +// +// SetRotationMode() +// set Android rotation mode +// +void Setting::SetRotationMode(int mode) +{ + if (mode < RotationAuto || mode > RotationPortrait) { + mode = RotationAuto; + } + rotation_mode = mode; +} // // GetSkipFrame() diff --git a/Source/UI/setting.h b/Source/UI/setting.h index 2e585ad..685947c 100644 --- a/Source/UI/setting.h +++ b/Source/UI/setting.h @@ -64,9 +64,14 @@ // // platform dependent // -class Setting -{ -public: +class Setting +{ +public: + enum RotationMode { + RotationAuto = 0, + RotationLandscape, + RotationPortrait + }; Setting(); // constructor virtual ~Setting(); @@ -149,8 +154,12 @@ class Setting // get image interpolation bool IsForceRGB565(); // get RGB565 mode (Android only) - void SetForceRGB565(bool enable); - // set RGB565 mode (Android only) + void SetForceRGB565(bool enable); + // set RGB565 mode (Android only) + int GetRotationMode(); + // get Android rotation mode + void SetRotationMode(int mode); + // set Android rotation mode // audio int GetAudioDevice(); @@ -288,8 +297,10 @@ class Setting // render scale quality (version 1.05) bool keyboard_enable; // keyboard enable (version 1.10) - bool force_rgb565; - // force RGB565 (version 1.20) + bool force_rgb565; + // force RGB565 (version 1.20) + int rotation_mode; + // Android rotation mode (version 2.00) bool watch_battery; // watch battery (version 1.30) uint32 joystick_to_key[15]; diff --git a/Source/UI/video.cpp b/Source/UI/video.cpp index d185dbd..b92a199 100644 --- a/Source/UI/video.cpp +++ b/Source/UI/video.cpp @@ -40,8 +40,10 @@ // drive status (2HD access) #define MINIMUM_HEIGHT 2 // minimum height -#define STATUS_HEIGHT 16 - // status area height +#define STATUS_HEIGHT 16 + // status area height +#define PORTRAIT_PANEL_MIN_SCALE 90 + // minimum lower-panel scale #define DRIVE_WIDTH (28 * 8) // drive area width #define FRAME_RATE_X 56 @@ -108,6 +110,7 @@ Video::Video(App *a) SDL_zero(src_rect); SDL_zero(status_rect); SDL_zero(softkey_rect); + SDL_zero(menu_rect); memset(clear_rect, 0, sizeof(clear_rect)); // drive status @@ -356,6 +359,13 @@ void Video::SetWindowSize(int width, int height) bool status; int v_height; int draw_height; + int game_height; + int panel_height; + int panel_width; + +#ifdef __ANDROID__ + int panel_min_height; +#endif // font and disk manager if (font == NULL) { @@ -365,29 +375,11 @@ void Video::SetWindowSize(int width, int height) diskmgr = app->GetDiskManager(); } - // save parameter + // save parameter window_width = width; window_height = height; - portrait_split = false; -#ifdef __ANDROID__ - portrait_split = height > width; -#endif - draw_height = portrait_split ? height / 2 : height; - if (portrait_split) { - softkey_rect.x = 0; - softkey_rect.y = draw_height; - softkey_rect.w = width; - softkey_rect.h = height - draw_height; - softkey_mod = 0; - if (softkey_texture != NULL) { - SDL_SetTextureAlphaMod(softkey_texture, 0xff); - } - } - else { - SDL_zero(softkey_rect); - } - - // update video_height + + // update video_height status = setting->HasStatusLine(); if (status == true) { v_height = SCREEN_HEIGHT + MINIMUM_HEIGHT + STATUS_HEIGHT; @@ -398,9 +390,46 @@ void Video::SetWindowSize(int width, int height) if (v_height != video_height) { video_height = v_height; - // rebuild texture (status only) - RebuildTexture(true); - } + // rebuild texture (status only) + RebuildTexture(true); + } + + // Android portrait game/control split. The game receives its full-width, + // aspect-correct height first. The lower panel may shrink uniformly to + // 90 percent, but smaller windows retain the legacy overlay layout. + portrait_split = false; + SDL_zero(softkey_rect); + SDL_zero(menu_rect); + game_height = 0; +#ifdef __ANDROID__ + panel_min_height = 0; + if (height > width) { + game_height = (width * video_height) / SCREEN_WIDTH; + panel_min_height = ((width * SCREEN_HEIGHT) / SCREEN_WIDTH) * + PORTRAIT_PANEL_MIN_SCALE / 100; + portrait_split = height >= game_height + panel_min_height; + } +#endif + if (portrait_split) { + panel_height = height - game_height; + panel_width = (panel_height * SCREEN_WIDTH) / SCREEN_HEIGHT; + if (panel_width > width) { + panel_width = width; + panel_height = (panel_width * SCREEN_HEIGHT) / SCREEN_WIDTH; + } + + softkey_rect.x = (width - panel_width) / 2; + softkey_rect.y = game_height + ((height - game_height - panel_height) / 2); + softkey_rect.w = panel_width; + softkey_rect.h = panel_height; + menu_rect = softkey_rect; + softkey_mod = 0; + if (softkey_texture != NULL) { + SDL_SetTextureAlphaMod(softkey_texture, 0xff); + } + } + + draw_height = portrait_split ? game_height : height; // check aspect if ((draw_height * SCREEN_WIDTH) >= (width * video_height)) { @@ -649,22 +678,34 @@ void Video::UpdateSoftKey() // ConvertPoint() // convert point from window to texture // -bool Video::ConvertPoint(int *x, int *y) -{ - int draw_x; - int draw_y; - - // get - draw_x = *x; - draw_y = *y; - - // offset - draw_x -= draw_rect.x; - draw_y -= draw_rect.y; - - // convert - draw_x = (draw_x * SCREEN_WIDTH) / draw_rect.w; - draw_y = (draw_y * video_height) / draw_rect.h; +bool Video::ConvertPoint(int *x, int *y) +{ + int draw_x; + int draw_y; + SDL_Rect *rect; + int logical_height; + + // Menus own the dedicated lower panel in Android portrait mode. + if (portrait_split == true && menu_mode == true) { + rect = &menu_rect; + logical_height = SCREEN_HEIGHT; + } + else { + rect = &draw_rect; + logical_height = video_height; + } + + // get + draw_x = *x; + draw_y = *y; + + // offset + draw_x -= rect->x; + draw_y -= rect->y; + + // convert + draw_x = (draw_x * SCREEN_WIDTH) / rect->w; + draw_y = (draw_y * logical_height) / rect->h; // over check if ((draw_x < 0) || (draw_x >= SCREEN_WIDTH)) { @@ -673,7 +714,7 @@ bool Video::ConvertPoint(int *x, int *y) return false; } - if ((draw_y < 0) || (draw_y >= video_height)) { + if ((draw_y < 0) || (draw_y >= logical_height)) { *x = 0; *y = 0; return false; @@ -1288,7 +1329,8 @@ void Video::DrawMenu(bool status) // menu texture if (ret == 0) { // menu texture - ret = SDL_RenderCopy(renderer, menu_texture, NULL, &draw_rect); + ret = SDL_RenderCopy(renderer, menu_texture, NULL, + portrait_split ? &menu_rect : &draw_rect); if (ret == 0) { SDL_RenderPresent(renderer); } diff --git a/Source/UI/video.h b/Source/UI/video.h index 175eac9..ef88bf2 100644 --- a/Source/UI/video.h +++ b/Source/UI/video.h @@ -159,6 +159,8 @@ class Video // status rect SDL_Rect softkey_rect; // dedicated softkey rect in portrait mode + SDL_Rect menu_rect; + // dedicated menu rect in portrait mode SDL_Rect clear_rect[2]; // clear rect Uint8 brightness;