Skip to content

Conversation

@narodnik
Copy link
Contributor

See this provided comment:

// This handler provides a default impl which resizes content when the
// IME is shown or hidden.
//
// However this will lead to flickers in your app as the content is
// resized before you have a chance to redraw it.
//
// The workaround for now is:
// * Get IME and system insets and send them to your app.
// * Notify your app to draw and apply the insets to fit.
// * Disable this function by returning early.

Basically before miniquad used to use the whole canvas and not resize when IME (keyboard in android speak) was shown/hidden. So then I added the ResizingLayout to make it automatically resize the canvas.

However this causes a noticeable flicker since the program flow goes like this:

ResizingLayout apply insets -> resize canvas -> surface changed -> native android -> resize event

during which time the screen is being drawn by android and whatever is there gets resized before the user has a chance to hit update()/draw() and redo the entire canvas.

The solution then in my case was to disable the screen resize and listen myself for the insets but this would then introduce complexity into miniquad. I've introduced these hooks for now as the lowest impact solution but I'm also open to alternative solutions if we think having this directly in miniquad is useful.

Here is my application code using these hooks:

java/MainActivity.java:

//% RESIZING_LAYOUT_ON_APPLY_WINDOW_INSETS

{
    Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
    Insets sysInsets = insets.getInsets(WindowInsets.Type.systemBars());

    // Screen: (1440, 3064)
    // IME height: 1056
    // Sys insets: (0, 152, 0, 135)

    onApplyInsets(
        sysInsets.left, sysInsets.top, sysInsets.right, sysInsets.bottom,
        imeInsets.left, imeInsets.top, imeInsets.right, imeInsets.bottom
    );
}
// Workaround for Java error due to remaining body.
// We handle the insets in our app directly.
if (true)
    return insets;

//% END

android ndk handler code:

use miniquad::native::android::{self, ndk_sys, ndk_utils};
use parking_lot::Mutex as SyncMutex;
use std::sync::LazyLock;

type Insets = [f32; 4];
type InsetsSender = async_channel::Sender<Insets>;

struct InsetsGlobals {
    sender: Option<InsetsSender>,
    insets: Insets,
}

static GLOBALS: LazyLock<SyncMutex<InsetsGlobals>> =
    LazyLock::new(|| SyncMutex::new(InsetsGlobals { sender: None, insets: [0.; 4] }));

pub fn set_sender(sender: InsetsSender) {
    GLOBALS.lock().sender = Some(sender);
}

pub fn get_insets() -> Insets {
    GLOBALS.lock().insets.clone()
}

#[no_mangle]
pub unsafe extern "C" fn Java_darkfi_darkfi_1app_ResizingLayout_onApplyInsets(
    _env: *mut ndk_sys::JNIEnv,
    _: ndk_sys::jobject,
    sys_left: ndk_sys::jint,
    sys_top: ndk_sys::jint,
    sys_right: ndk_sys::jint,
    sys_bottom: ndk_sys::jint,
    ime_left: ndk_sys::jint,
    ime_top: ndk_sys::jint,
    ime_right: ndk_sys::jint,
    ime_bottom: ndk_sys::jint,
) {
    debug!(
        target: "android::insets",
        "onApplyInsets() \
            sys=({sys_left}, {sys_top}, {sys_right}, {sys_bottom}) \
            ime=({ime_left}, {ime_top}, {ime_right}, {ime_bottom}) \
        )"
    );
    let mut globals = GLOBALS.lock();
    globals.insets = [sys_left as f32, sys_top as f32, sys_right as f32, sys_bottom as f32];
    if ime_bottom > 0 {
        globals.insets[3] = ime_bottom as f32;
    }
    if let Some(sender) = &globals.sender {
        let _ = sender.try_send(globals.insets.clone());
    } else {
        warn!(target: "android::insets", "Dropping insets notify since no sender is set");
    }
}

This way the canvas remains unchanged. It is my user code that does the resizing. Another benefit is I can draw below the navigation and system bars too.

Also check not-fl3/cargo-quad-apk#13

@narodnik
Copy link
Contributor Author

narodnik commented Jan 1, 2026

An alternative solution is that in miniquad we keep the canvas unchanged (like I'm doing) and instead fire resize events with the newly calculated screen size (after insets).

However this would make GL calculations for transform matrixes wrong since we need the full canvas size. So then we would have to maybe introduce a separate event handler like apply_insets_event() or canvas_resize_event() for the "inner" drawing space.

Let me know which direction you prefer. I just picked this for now as the lowest impact on miniquad, but happy to workout other ideas.

Anyway regardless we should still merge this since having the ResizingLayout injection hooks is useful for users to override behaviour.

@not-fl3
Copy link
Owner

not-fl3 commented Jan 1, 2026

I am so behind your miniquad/android experience at this point to be honest, you say its useful - I merge 🫡

Thanks for PR!

@not-fl3 not-fl3 merged commit dd44380 into not-fl3:master Jan 1, 2026
11 checks passed
@narodnik
Copy link
Contributor Author

narodnik commented Jan 2, 2026

Thanks, I will prepare a doc at some point with all my findings. First I also want to explore the iOS experience more too so I can compare them both.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants