bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
36.25k stars 3.58k forks source link

Ability to hide navigation buttons on Android (immersive mode) #11402

Open eero-lehtinen opened 10 months ago

eero-lehtinen commented 10 months ago

What problem does this solve or what need does it fill?

I think most games would prefer to have the game be full screen instead of showing the title bar and navigation buttons. It is already possible to hide the title bar through Android build configuration in cargo-apk or xbuild, but hiding the navigation buttons requires calling Java functions.

What solution would you like?

A boolean paremeter on WindowPlugin would be nice to enable or disable this feature. I think it should be on by default for games.

What alternative(s) have you considered?

You can always implement it yourself, but it's not trivial.

Additional context

I have crated this monstrosity to achieve this while being very inexperienced in Android APIs. I'm not sure if this works for all Android versions but seems to work for me. There is also WindowInsetsControllerCompat, which could have better compatibility, but I couldn't get it to work without crashing. There is an official guide on how to do this differently, but this simple way seems to be enough.

use jni::objects::{JObject, JValue};

/// Needs to be called from the main thread
pub fn enable_immersive_mode() -> anyhow::Result<()> {
    let android_app = bevy::winit::ANDROID_APP.get().unwrap();
    let vm = unsafe { jni::JavaVM::from_raw(android_app.vm_as_ptr() as *mut *const _)? };
    let activity = unsafe { JObject::from_raw(android_app.activity_as_ptr() as *mut _) };
    let mut env = vm.attach_current_thread()?;

    // getWindow()
    let window = env
        .call_method(activity, "getWindow", "()Landroid/view/Window;", &[])?
        .l()?;

    // getWindow().getInsetsController()
    let insets_controller = env
        .call_method(
            &window,
            "getInsetsController",
            "()Landroid/view/WindowInsetsController;",
            &[],
        )?
        .l()?;

    // getWindow().getInsetsController().hide(2)
    env.call_method(&insets_controller, "hide", "(I)V", &[JValue::from(2)])?;

    Ok(())
}
woncomp commented 5 months ago

This is super helpful!

For my case, env.call_method(&insets_controller, "hide", "(I)V", &[JValue::from(3)])?; did the trick.

The value is supposed to be retrieved by calling WindowInsetsCompat.Type.systemBars(), I guess it could actually be different values on different devices.