diff --git a/cocos/core/platform/macro.ts b/cocos/core/platform/macro.ts index 612e15e4a0e..b95a8192df9 100644 --- a/cocos/core/platform/macro.ts +++ b/cocos/core/platform/macro.ts @@ -28,6 +28,7 @@ import { EDITOR, MINIGAME, NATIVE, PREVIEW, RUNTIME_BASED } from 'internal:constants'; import { legacyCC } from '../global-exports'; import { Settings, settings } from '../settings'; +import { Orientation } from '../../../pal/screen-adapter/enum-type'; const SUPPORT_TEXTURE_FORMATS = ['.astc', '.pkm', '.pvr', '.webp', '.jpg', '.jpeg', '.bmp', '.png']; @@ -903,11 +904,29 @@ interface Macro { ORIENTATION_PORTRAIT: number; /** - * @en Oriented horizontally - * @zh 横屏朝向 + * @en Inverted portrait orientation, with the device bottom facing upward. + * @zh 倒置竖屏,设备底部朝上 + */ + ORIENTATION_PORTRAIT_UPSIDE_DOWN: number; + + /** + * @en Oriented horizontally. Users cannot directly listen to this value; they need to listen for ORIENTATION_LANDSCAPE_LEFT or ORIENTATION_LANDSCAPE_RIGHT. + * @zh 横屏朝向, 外部不能直接监听该值,需要监听 ORIENTATION_LANDSCAPE_LEFT 或 ORIENTATION_LANDSCAPE_RIGHT */ ORIENTATION_LANDSCAPE: number; + /** + * @en Oriented horizontally, top side facing to the left. Supported only on mobile devices. + * @zh 水平方向,顶部朝向左侧;仅移动端支持 + */ + ORIENTATION_LANDSCAPE_LEFT: number; + + /** + * @en Oriented horizontally, top side facing to the right. Supported only on mobile devices. + * @zh 水平方向,顶部朝向右侧;仅移动端支持 + */ + ORIENTATION_LANDSCAPE_RIGHT: number; + /** * @en Oriented automatically * @zh 自动适配朝向 @@ -1091,9 +1110,12 @@ const macro: Macro = { DEG: 180 / Math.PI, REPEAT_FOREVER: (Number.MAX_VALUE - 1), FLT_EPSILON: 0.0000001192092896, - ORIENTATION_PORTRAIT: 1, - ORIENTATION_LANDSCAPE: 2, - ORIENTATION_AUTO: 3, + ORIENTATION_PORTRAIT: Orientation.PORTRAIT, + ORIENTATION_PORTRAIT_UPSIDE_DOWN: Orientation.PORTRAIT_UPSIDE_DOWN, + ORIENTATION_LANDSCAPE: Orientation.LANDSCAPE, + ORIENTATION_LANDSCAPE_LEFT: Orientation.LANDSCAPE_LEFT, + ORIENTATION_LANDSCAPE_RIGHT: Orientation.LANDSCAPE_RIGHT, + ORIENTATION_AUTO: Orientation.AUTO, ENABLE_TILEDMAP_CULLING: true, TOUCH_TIMEOUT: 5000, ENABLE_TRANSPARENT_CANVAS: false, diff --git a/cocos/ui/view.ts b/cocos/ui/view.ts index 09230bbf4d7..c06d364fcce 100644 --- a/cocos/ui/view.ts +++ b/cocos/ui/view.ts @@ -30,7 +30,7 @@ import { MINIGAME, JSB, RUNTIME_BASED, EDITOR } from 'internal:constants'; import { screenAdapter } from 'pal/screen-adapter'; import { Eventify } from '../core/event'; import { Rect, Size, Vec2 } from '../core/math'; -import { visibleRect, cclegacy, errorID, screen, macro, System } from '../core'; +import { visibleRect, cclegacy, errorID, screen, macro, System, assert } from '../core'; import { Orientation } from '../../pal/screen-adapter/enum-type'; import { director } from '../game/director'; import { Settings, settings } from '../core/settings'; @@ -140,7 +140,6 @@ export class View extends Eventify(System) { // For now, the engine UI is adapted to resolution size, instead of window size. screen.on('window-resize', this._updateAdaptResult, this); - screen.on('orientation-change', this._updateAdaptResult, this); screen.on('fullscreen-change', this._updateAdaptResult, this); } @@ -596,8 +595,10 @@ export class View extends Eventify(System) { const w = this._designResolutionSize.width; const h = this._designResolutionSize.height; - if (width > 0) { + if (width > 0 && height > 0) { this.setDesignResolutionSize(w, h, this._resolutionPolicy); + } else { + assert(false, '_updateAdaptResult Invalid size.'); } this.emit('canvas-resize'); diff --git a/native/cocos/bindings/event/EventDispatcher.cpp b/native/cocos/bindings/event/EventDispatcher.cpp index 9ebc32a28bc..0d267a26531 100644 --- a/native/cocos/bindings/event/EventDispatcher.cpp +++ b/native/cocos/bindings/event/EventDispatcher.cpp @@ -40,6 +40,7 @@ se::Object *jsKeyboardEventObj = nullptr; se::Object *jsControllerEventArray = nullptr; se::Object *jsControllerChangeEventArray = nullptr; se::Object *jsResizeEventObj = nullptr; +se::Object *jsOrientationChangeObj = nullptr; bool inited = false; bool busListenerInited = false; @@ -145,6 +146,12 @@ void EventDispatcher::destroy() { jsResizeEventObj = nullptr; } + if (jsOrientationChangeObj != nullptr) { + jsOrientationChangeObj->unroot(); + jsOrientationChangeObj->decRef(); + jsOrientationChangeObj = nullptr; + } + inited = false; tickVal.setUndefined(); } @@ -412,7 +419,17 @@ void EventDispatcher::dispatchResizeEvent(int width, int height, uint32_t window } void EventDispatcher::dispatchOrientationChangeEvent(int orientation) { - // Ts's logic is same as the 'onResize', so remove code here temporary. + se::AutoHandleScope scope; + if (!jsOrientationChangeObj) { + jsOrientationChangeObj = se::Object::createPlainObject(); + jsOrientationChangeObj->root(); + } + + jsOrientationChangeObj->setProperty("orientation", se::Value(orientation)); + + se::ValueArray args; + args.emplace_back(se::Value(jsOrientationChangeObj)); + EventDispatcher::doDispatchJsEvent("onOrientationChanged", args); } void EventDispatcher::dispatchEnterBackgroundEvent() { diff --git a/native/cocos/platform/android/AndroidPlatform.cpp b/native/cocos/platform/android/AndroidPlatform.cpp index 8c2733eb949..d1bd348a45d 100644 --- a/native/cocos/platform/android/AndroidPlatform.cpp +++ b/native/cocos/platform/android/AndroidPlatform.cpp @@ -51,6 +51,8 @@ #include "base/StringUtil.h" #include "engine/EngineEvents.h" #include "paddleboat.h" +#include "platform/java/jni/JniImp.h" +#include "platform/interfaces/modules/Device.h" #define ABORT_GAME \ { \ @@ -531,6 +533,7 @@ class GameInputProxy { if (!_launched) { _launched = true; + _androidPlatform->_rotation = getDeviceRotationJNI(); ISystemWindowInfo info; info.width = ANativeWindow_getWidth(nativeWindow); info.height = ANativeWindow_getHeight(nativeWindow); @@ -626,13 +629,35 @@ class GameInputProxy { events::WindowEvent::broadcast(ev); break; } - case APP_CMD_CONFIG_CHANGED: + case APP_CMD_CONFIG_CHANGED: { CC_LOG_INFO("AndroidPlatform: APP_CMD_CONFIG_CHANGED"); + int rotation = getDeviceRotationJNI(); + if (_androidPlatform->_rotation != rotation) { + CC_LOG_INFO("AndroidPlatform: orientation-change"); + _androidPlatform->_rotation = rotation; + int orientation = static_cast(Device::Orientation::PORTRAIT); + switch (rotation) { + case 0: // ROTATION_0 + orientation = static_cast(Device::Orientation::PORTRAIT); + break; + case 1: // ROTATION_90 + orientation = static_cast(cc::Device::Orientation::LANDSCAPE_RIGHT); + break; + case 2: // ROTATION_180 + orientation = static_cast(cc::Device::Orientation::PORTRAIT_UPSIDE_DOWN); + break; + case 3: // ROTATION_270 + orientation = static_cast(cc::Device::Orientation::LANDSCAPE_LEFT); + break; + } + cc::events::Orientation::broadcast(orientation); + } // Window was resized or some other configuration changed. // Note: we don't handle this event because we check the surface dimensions // every frame, so that's how we know it was resized. If you are NOT doing that, // then you need to handle this event! break; + } case APP_CMD_LOW_MEMORY: { // system told us we have low memory. So if we are not visible, let's // cooperate by deallocating all of our graphic resources. diff --git a/native/cocos/platform/android/AndroidPlatform.h b/native/cocos/platform/android/AndroidPlatform.h index de550027b44..55d0a853da3 100644 --- a/native/cocos/platform/android/AndroidPlatform.h +++ b/native/cocos/platform/android/AndroidPlatform.h @@ -68,5 +68,6 @@ class CC_DLL AndroidPlatform : public UniversalPlatform { GameInputProxy *_inputProxy{nullptr}; android_app *_app{nullptr}; friend class GameInputProxy; + int _rotation{-1}; }; } // namespace cc diff --git a/native/cocos/platform/android/java/src/com/cocos/lib/CocosActivity.java b/native/cocos/platform/android/java/src/com/cocos/lib/CocosActivity.java index a20829e1e02..bd27d9f10a3 100644 --- a/native/cocos/platform/android/java/src/com/cocos/lib/CocosActivity.java +++ b/native/cocos/platform/android/java/src/com/cocos/lib/CocosActivity.java @@ -24,6 +24,7 @@ package com.cocos.lib; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.media.AudioManager; @@ -45,6 +46,7 @@ public class CocosActivity extends GameActivity { private static final String TAG = "CocosActivity"; + private static final int INITIAL_ROTATION = -1; private CocosWebViewHelper mWebViewHelper = null; private CocosVideoHelper mVideoHelper = null; @@ -52,6 +54,8 @@ public class CocosActivity extends GameActivity { private List mSurfaceViewArray; private FrameLayout mRootLayout; + private int mRotation = INITIAL_ROTATION; + private native void onCreateNative(); @@ -172,6 +176,18 @@ protected void onStart() { surfaceView.setVisibility(View.VISIBLE); } } + if (mRotation == INITIAL_ROTATION + && getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { + //onConfigurationChange can be triggered at the mode of 'sensor or fullSensor'. Here only handles the sensorLandscape mode. + mRotation = CocosHelper.getDeviceRotation(); + mSurfaceView.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + int rotation = CocosHelper.getDeviceRotation(); + if (mRotation != rotation) { + mRotation = rotation; + this.onConfigurationChangedNative(this.getGameActivityNativeHandle()); + } + }); + } } @Override diff --git a/native/cocos/platform/java/jni/JniCocosOrientationHelper.cpp b/native/cocos/platform/java/jni/JniCocosOrientationHelper.cpp index 804be6d0670..a99fd96cb93 100644 --- a/native/cocos/platform/java/jni/JniCocosOrientationHelper.cpp +++ b/native/cocos/platform/java/jni/JniCocosOrientationHelper.cpp @@ -36,12 +36,16 @@ JNIEXPORT void JNICALL Java_com_cocos_lib_CocosOrientationHelper_nativeOnOrienta switch (rotation) { case 0: // ROTATION_0 orientation = (int)cc::Device::Orientation::PORTRAIT; + break; case 1: // ROTATION_90 orientation = (int)cc::Device::Orientation::LANDSCAPE_RIGHT; + break; case 2: // ROTATION_180 orientation = (int)cc::Device::Orientation::PORTRAIT_UPSIDE_DOWN; + break; case 3: // ROTATION_270 orientation = (int)cc::Device::Orientation::LANDSCAPE_LEFT; + break; } // run callbacks in game thread? cc::events::Orientation::broadcast(orientation); diff --git a/pal/screen-adapter/native/screen-adapter.ts b/pal/screen-adapter/native/screen-adapter.ts index 85b2b79d71b..f145bf99bd0 100644 --- a/pal/screen-adapter/native/screen-adapter.ts +++ b/pal/screen-adapter/native/screen-adapter.ts @@ -176,7 +176,7 @@ class ScreenAdapter extends EventTarget { this.emit('window-resize', event.width, event.height, event.windowId); }; jsb.onOrientationChanged = (event): void => { - this.emit('orientation-change'); + this.emit('orientation-change', this.orientation); }; } } diff --git a/pal/screen-adapter/web/screen-adapter.ts b/pal/screen-adapter/web/screen-adapter.ts index 7b358bc937b..c82b71d505f 100644 --- a/pal/screen-adapter/web/screen-adapter.ts +++ b/pal/screen-adapter/web/screen-adapter.ts @@ -377,11 +377,21 @@ class ScreenAdapter extends EventTarget { }); window.addEventListener('resize', (): void => { - if (!this.handleResizeEvent) { - return; - } + // if (!this.handleResizeEvent) { + // return; + // } this._resizeFrame(); }); + + const adapter = this; + const notifyOrientationChange = (orientation): void => { + if (orientation === adapter._orientation) { + return; + } + adapter._orientation = orientation; + adapter.emit('orientation-change', orientation); + }; + if (typeof window.matchMedia === 'function') { const updateDPRChangeListener = (): void => { const dpr = window.devicePixelRatio; @@ -392,21 +402,53 @@ class ScreenAdapter extends EventTarget { }, { once: true }); }; updateDPRChangeListener(); - } - window.addEventListener('orientationchange', (): void => { - if (this._orientationChangeTimeoutId !== -1) { - clearTimeout(this._orientationChangeTimeoutId); - } - this._orientationChangeTimeoutId = setTimeout((): void => { - if (!this.handleResizeEvent) { - return; + + const mediaQueryPortrait = window.matchMedia('(orientation: portrait)'); + const mediaQueryLandscape = window.matchMedia('(orientation: landscape)'); + const handleOrientationChange = (): void => { + let tmpOrientation = adapter._orientation; + const orientationType = screen.orientation.type; + if (mediaQueryPortrait.matches) { + if (orientationType === 'portrait-primary') { + tmpOrientation = Orientation.PORTRAIT; + } else { + tmpOrientation = Orientation.PORTRAIT_UPSIDE_DOWN; + } + } else { + if (orientationType === 'landscape-primary') { + tmpOrientation = Orientation.LANDSCAPE_LEFT; + } else if (orientationType === 'landscape-secondary') { + tmpOrientation = Orientation.LANDSCAPE_RIGHT; + } } - this._updateFrameState(); - this._resizeFrame(); - this.emit('orientation-change', this.windowSize.width, this.windowSize.height); - this._orientationChangeTimeoutId = -1; - }, EVENT_TIMEOUT); - }); + notifyOrientationChange(tmpOrientation); + }; + mediaQueryPortrait.addEventListener('change', handleOrientationChange); + mediaQueryLandscape.addEventListener('change', handleOrientationChange); + } else { + const adapter = this; + const handleOrientationChange = (): void => { + let tmpOrientation = adapter._orientation; + switch (window.orientation) { + case 0: + tmpOrientation = Orientation.PORTRAIT; + break; + case 90: + // Handle landscape orientation, top side facing to the right + tmpOrientation = Orientation.LANDSCAPE_RIGHT; + break; + case -90: + // Handle landscape orientation, top side facing to the left + tmpOrientation = Orientation.LANDSCAPE_LEFT; + break; + case 180: + tmpOrientation = Orientation.PORTRAIT_UPSIDE_DOWN; + break; + } + notifyOrientationChange(tmpOrientation); + }; + window.addEventListener('orientationchange', handleOrientationChange); + } document.addEventListener(this._fn.fullscreenchange, () => { this._onFullscreenChange?.(); this.emit('fullscreen-change', this.windowSize.width, this.windowSize.height); @@ -440,7 +482,13 @@ class ScreenAdapter extends EventTarget { this._gameFrame.style.width = `${sizeInCssPixels.width}px`; this._gameFrame.style.height = `${sizeInCssPixels.height}px`; } else { - const winWidth = window.innerWidth; const winHeight = window.innerHeight; + const winWidth = window.innerWidth; + let winHeight = window.innerHeight; + //On certain devices, window.innerHeight may not account for the height of the virtual keyboard, so dynamic calculation is necessary. + const inputHeight = document.body.scrollHeight - winHeight; + if (winHeight < inputHeight) { + winHeight += inputHeight; + } if (this.isFrameRotated) { this._gameFrame.style['-webkit-transform'] = 'rotate(90deg)'; this._gameFrame.style.transform = 'rotate(90deg)'; @@ -504,7 +552,10 @@ class ScreenAdapter extends EventTarget { const height = window.innerHeight; const isBrowserLandscape = width > height; this.isFrameRotated = systemInfo.isMobile - && ((isBrowserLandscape && orientation === Orientation.PORTRAIT) || (!isBrowserLandscape && orientation === Orientation.LANDSCAPE)); + && ((isBrowserLandscape && orientation === Orientation.PORTRAIT) || + (!isBrowserLandscape && (orientation === Orientation.LANDSCAPE || + orientation === Orientation.LANDSCAPE_LEFT || + orientation === Orientation.LANDSCAPE_RIGHT))); } private _updateContainer (): void { if (!this._gameContainer) {