Skip to content

Android: implement Window::safe_area#4506

Open
nicoburns wants to merge 1 commit intorust-windowing:masterfrom
nicoburns:android-safe-area
Open

Android: implement Window::safe_area#4506
nicoburns wants to merge 1 commit intorust-windowing:masterfrom
nicoburns:android-safe-area

Conversation

@nicoburns
Copy link
Copy Markdown
Contributor

Implements Window::safe_area for Android. This replaces the platform-specific WindowExtAndroid::content_rect method.

Android provides the top and right coordinates as offsets from the top left (0, 0) point rather than as insets from the nearest side. So these are subtracted from the outer size to normalize them according the existing semantics of Window::safe_area.

  • Tested on all platforms changed
  • Added an entry to the changelog module if knowledge of this change could be valuable to users
  • Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
  • Created or updated an example program if it would help users understand this functionality

@nicoburns nicoburns requested a review from MarijnS95 as a code owner March 7, 2026 01:09
@nicoburns nicoburns force-pushed the android-safe-area branch from 19e5226 to 11166d1 Compare March 7, 2026 01:11
@nicoburns nicoburns changed the title Implement Window::safe_area for android Android: implement Window::safe_area Mar 7, 2026
Signed-off-by: Nico Burns <nico@nicoburns.com>
@nicoburns nicoburns force-pushed the android-safe-area branch from 11166d1 to bd06339 Compare March 7, 2026 01:14
@madsmtm madsmtm added the DS - android Affects the Android backend label Mar 7, 2026
Copy link
Copy Markdown
Member

@madsmtm madsmtm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to hear from @MarijnS95, but if he hasn't found time for it in two weeks, ping me then, I'd be comfortable merging this, in any case it's better than nothing.

MainEvent::ContentRectChanged { .. } => {
warn!("TODO: find a way to notify application of content rect change");
},
MainEvent::ContentRectChanged { .. } => pending_redraw = true,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be resized = true? That'd better match what we (somewhat) guarantee on macOS (that changes to the safe area trigger a resize event).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We definitely could do a resize event. I want to react to "safe area" changes very differently to surface resizes (notably, I don't want to resize the underlying texture, because at least on Android I still want to render a background color into the safe area). But I could implement this using an equality check on the "surface size" and "safe area" in the resize handler.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'll want #4507, but for now the behavior we have on iOS and macOS is to emit a resize event so let's match that on Android too.

(To be clear, the resize will still contain the surface size as before, it's just a signal to the app to update other things that depend on the safe area, I know Alacritty relies on it).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait nvmd, iOS only requests a redraw. So I'm fine with only doing that here too.

@madsmtm madsmtm added the S - platform parity Unintended platform differences label Mar 17, 2026
Copy link
Copy Markdown
Member

@madsmtm madsmtm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re semantics, content_rect() differs from iOS in that it includes changes to the soft keyboard, doesn't it? If so, I'm not sure we should use that, I don't think you'd want to render your content differently depending on if the keyboard is present on screen or not? If it overflows, you should instead provide a scroll bar.

@nicoburns
Copy link
Copy Markdown
Contributor Author

nicoburns commented Mar 17, 2026

Re semantics, content_rect() differs from iOS in that it includes changes to the soft keyboard, doesn't it?

Yes, this is correct, and a good point.

I don't think you'd want to render your content differently depending on if the keyboard is present on screen or not?

I definitely do want to render my content differently depending on whether the keyboard is present on the screen or not (at least sometimes). Consider how the URL bar of mobile browsers typically works (when it is at the bottom of the screen), or how some apps have an extra toolbar above the keyboard. However, I probably want to separately react to this "content area" and the "safe area" that is taken up by non-keyboard stuff (on Android this is the toolbars).

Is there a way to get the "content area" (modified by keyboard) on iOS? Perhaps we could expose both of these separately on both platforms?

See also https://reactnative.dev/docs/keyboardavoidingview

If it overflows, you should instead provide a scroll bar.

How can I know if it overflows if I don't know the "keyboard avoiding size"?

@madsmtm madsmtm mentioned this pull request Mar 17, 2026
9 tasks
@madsmtm
Copy link
Copy Markdown
Member

madsmtm commented Mar 17, 2026

I agree that you want to do things differently depending on whether the soft keyboard is open or not, I meant more that "safe area is probably the wrong tool for this".

For example, opening the keyboard is animated, you might want to animate your content with it.

I'm also reading this, content_rect corresponds to WindowInsets.safeContent right?


Then again, SwiftUI provides .ignoresSafeArea(.keyboard), which means that by default it seems to include the soft keyboard in the safe area?

So maybe the right thing to do would be to include it on iOS too (and later allow querying the size of the soft keyboard to "opt out").

@nicoburns
Copy link
Copy Markdown
Contributor Author

I'm also reading this, content_rect corresponds to WindowInsets.safeContent right?

Yes, I believe so.

For example, opening the keyboard is animated, you might want to animate your content with it.

Yes, I would want to animate my content. And in testing I found I was able to so by rerendering with a new size in response to SafeAreaChanged events as in #4507.

Then again, SwiftUI provides .ignoresSafeArea(.keyboard), which means that by default it seems to include the soft keyboard in the safe area?

Is there a way to query (and subscribe to) the .keyboard size on iOS? (I can see that ignoresSafeArea accepts SafeAreaRegions, but safeAreaInset doesn't seem to...). Perhaps the model on iOS is that you statefully set the SafeAreaRegions that the view conforms to, and then it that area you get back when you query/subscribe?

If so, then perhaps we should model that API in Winit, as I think that ought to be implementable on Android too. If the sizes are (non-statefully) separately queryable and subscribable, then it may make sense to just subscribe to both and notify when either changes?

@madsmtm
Copy link
Copy Markdown
Member

madsmtm commented Mar 17, 2026

Don't look too hard at SwiftUI, I'm using it mostly as a reference for "how could we design the API", not "what data is actually available".

I think the way to do this (while supporting animations) on iOS would be to:

@madsmtm
Copy link
Copy Markdown
Member

madsmtm commented Mar 17, 2026

For just querying it: if we do step 1 and 2 (set adjustedContentInset in notifications), then querying is automatically correct afterwards.

Do you wanna take a stab at implementing this? If not, I can probably try.

@nicoburns
Copy link
Copy Markdown
Contributor Author

Do you wanna take a stab at implementing this? If not, I can probably try.

I would, but I'm already lost in the documentation. So perhaps you should try.

@madsmtm
Copy link
Copy Markdown
Member

madsmtm commented Mar 18, 2026

I've put up #4528 with my work on that, won't get to finishing it today.

@madsmtm
Copy link
Copy Markdown
Member

madsmtm commented Mar 18, 2026

I think it might make sense to consider whether/how we might expose the keyboard insets separately from the device/systemBars insets? Like, what would such an API look like?

Something like this:

trait Window {
    fn keyboard_insets(&self) -> PhysicalInsets<u32>,
}

Wouldn't really work, because window.safe_area() - window.keyboard_insets() would compute the wrong thing (bottom would be the same value, unless we defined keyboard_insets to be "relative" to the device/systemBars insets).

@madsmtm
Copy link
Copy Markdown
Member

madsmtm commented Mar 18, 2026

Do you know how to query the other kinds of insets on Android with native-activity/ndk?

@nicoburns
Copy link
Copy Markdown
Contributor Author

Do you know how to query the other kinds of insets on Android with native-activity/ndk?

No. I had a quick look, and it doesn't look to me like there is a way. A lot of Rust GUI apps/toolkits are using custom activities on Android. Dioxus Native does. Slint does. GPUI (unofficial mobile port) does. It may be worth looking at doing a Java-based backend for Winit...

@nicoburns
Copy link
Copy Markdown
Contributor Author

I think it might make sense to consider whether/how we might expose the keyboard insets separately from the device/systemBars insets?

Agree. As a user, this is what I would like!

Like, what would such an API look like?

I was imagining that there would be two methods returning PhysicalInsets<u32>. Both (independently) relative to the surface. The user might need to do a little math (but they may well do anyway - I know I'm converting the insets into (x, y, width, height)), but that would give them all the information.

@madsmtm
Copy link
Copy Markdown
Member

madsmtm commented Mar 18, 2026

I was imagining that there would be two methods returning PhysicalInsets<u32>. Both (independently) relative to the surface. The user might need to do a little math (but they may well do anyway - I know I'm converting the insets into (x, y, width, height)), but that would give them all the information.

So Window::container_insets and Window::keyboard_insets? (using SwiftUI naming here)

And then Window::safe_area would be window.container_insets().max(window.keyboard_insets())?

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

Labels

DS - android Affects the Android backend S - platform parity Unintended platform differences

Development

Successfully merging this pull request may close these issues.

2 participants