Skip to content

Add RawInput::safe_area and handle safe area on iOS in eframe#4915

Closed
frederik-uni wants to merge 10 commits intoemilk:mainfrom
frederik-uni:iOS-safe-area-widget
Closed

Add RawInput::safe_area and handle safe area on iOS in eframe#4915
frederik-uni wants to merge 10 commits intoemilk:mainfrom
frederik-uni:iOS-safe-area-widget

Conversation

@frederik-uni
Copy link
Copy Markdown
Contributor

Before:
Screenshot 2024-08-04 at 23 24 07
After:
Screenshot 2024-08-04 at 23 22 51

@emilk
Copy link
Copy Markdown
Owner

emilk commented Aug 5, 2024

I think this should be part of eframe, i.e. so you can call eframe::safe_area_top(ctx) to get how many ui points from the top it is safe to put widgets.

@frederik-uni
Copy link
Copy Markdown
Contributor Author

frederik-uni commented Aug 5, 2024

@emilk so, I move the safe area insets function to eframe, but what about the safe area widget. I think it would be better to use something like this instead of a widget:

fn safe_area<R>(ui: &mut Ui, inner: impl FnOnce(&mut Ui) -> R) -> R {
    let insets = get_ios_safe_area_insets();
    if let Some(insets) = insets {
        let cursor = ui.cursor().min;
        /*
        let x = insets.left - cursor.x as f64;
        if x > 0. {
            let (id, rect) = ui.allocate_space(vec2(x as f32, ui.available_height()));
            ui.interact(rect, id, Sense::hover());
        }
        */
        let y = insets.top - cursor.y as f64;
        if y > 0. {
            let (id, rect) = ui.allocate_space(vec2(ui.available_width(), y as f32));
            ui.interact(rect, id, Sense::hover());
        }
        let max_height = ui.available_height() - insets.bottom as f32;
        if max_height > 0.0 {
            ui.set_max_height(max_height);
        }
        let max_width = ui.available_width() - insets.right as f32;
        if max_width > 0.0 {
            ui.set_max_width(max_width);
        }
    }
    inner(ui)
}

where should I put it? should this be part of egui/egui_extras. should this & the safe area insets function be hidden behind a feature or should it be enabled for every iOS build? its kinda essential and probably needed in every iOS build anyway. And this example above will not work when the inset is != 0 on the left. do you have any ideas how I could get this to work properly?

Something like this would be great ui.within(rect, |ui| {});

@emilk
Copy link
Copy Markdown
Owner

emilk commented Aug 26, 2024

One approach would be to modify FrameState::available_rect so that is is the safe area. That would mean egui::CentralPanel (and other panels) would start within that smaller rect. However, one should still paint some sort of background color outside that.

@emilk emilk added the iOS label Aug 26, 2024
@lucasmerlin
Copy link
Copy Markdown
Collaborator

lucasmerlin commented Sep 16, 2024

Seems like winit will soon have a safe area api: rust-windowing/winit#3890
Maybe we can use that one instead? Then other platforms will also be supported once support is added to winit (rust-windowing/winit#3910)

@frederik-uni frederik-uni marked this pull request as draft September 16, 2024 12:06
@github-actions
Copy link
Copy Markdown

github-actions bot commented Oct 2, 2024

Preview available at https://egui-pr-preview.github.io/pr/4915-iOS-safe-area-widget
Note that it might take a couple seconds for the update to show up after the preview_build workflow has completed.

@frederik-uni frederik-uni marked this pull request as ready for review October 2, 2024 16:50
@frederik-uni
Copy link
Copy Markdown
Contributor Author

Im not quite sure when which events fires in winit. I guessed. theoretically WindowEvent::Resized(_) & ScaleFactorChanged should be enough, but im not sure if the events fire when the app is minimized or repopend in split screen or if the device was flipped while the screen was of so included Focused(true) & Occluded(false).

Im not quite sure when the safe area gets merged into winit as it had no updates the last 3 weeks. Migrating from my implementation to the winit implementation should be quite easy anyway(self.egui_input_mut().safe_area = Some(egui::SafeArea::from(safe_area::get_ios_safe_area_insets())); would need to be changed to self.egui_input_mut().safe_area = Some(egui::SafeArea::from(window.get_safe_area()))

I don't know how that got here
@lucasmerlin
Copy link
Copy Markdown
Collaborator

The winit PR was approved and is added to the 0.31 milestone so I think it makes sense to just wait for that and then update your PR with the winit implementation

@emilk emilk marked this pull request as draft October 29, 2024 09:30
@frederik-uni
Copy link
Copy Markdown
Contributor Author

@emilk the code was merged so once the version bump is in crates.io & this project is updated, this can be merged without change

@hacknus
Copy link
Copy Markdown
Contributor

hacknus commented Apr 18, 2025

Cool, Can this be merged now?

@lucasmerlin
Copy link
Copy Markdown
Collaborator

lucasmerlin commented Apr 19, 2025

Sorry, I didn't expect the winit 0.31 release to take so long. I'm fine with merging the objc approach. Will look into it next week.

@lucasmerlin lucasmerlin self-assigned this Apr 19, 2025
# Conflicts:
#	Cargo.lock
#	crates/egui/src/pass_state.rs
@lucasmerlin
Copy link
Copy Markdown
Collaborator

lucasmerlin commented Apr 22, 2025

I've merged master and reverted the commit relying on winit's safe area implementation, and tested it with my egui ios demo, works well!

ScreenRecording_04-22-2025.14-17-35_1.mov

Only thing I noticed is that windows and popups can extend past the safe area, which I don't think it should. It could mean e.g. a combobox popup is partially hidden behind the bottom bar thingy.

It'd also be nice if the keyboard opening would affect the safe area so that the ui could adjust accordingly.

…t `Area`s being able to move outside the safe area.
@lucasmerlin lucasmerlin marked this pull request as ready for review April 22, 2025 15:28
@lucasmerlin lucasmerlin added the feature New feature or request label Apr 22, 2025
@lucasmerlin lucasmerlin added egui eframe Relates to epi and eframe labels Apr 22, 2025
@lucasmerlin lucasmerlin changed the title iOS safe area Add RawInput::safe_area and handle safe area on iOS in eframe Apr 22, 2025
@lucasmerlin
Copy link
Copy Markdown
Collaborator

ScreenRecording_04-22-2025.17-32-43_1.MP4

Works really well now!

I've updated the way safe area is handled, it now maches screen_rect in that it's only updated if it's Some. Also I made the InputState::screen_rect field private and added a getter fn that respects the safe area and added screen_rect_including_unsafe_area which returns the unmodified screen rect. Not sure on the naming but I couldn't come up with something nicer.

@lucasmerlin lucasmerlin requested a review from emilk April 22, 2025 15:43
Comment on lines +10 to +17
/// Safe area insets of the screen. I.e., area not covered by the status bar, navigation bar, etc.
#[derive(Debug, PartialEq, Copy, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct SafeArea {
pub top: f32,
pub right: f32,
pub bottom: f32,
pub left: f32,
Copy link
Copy Markdown
Owner

@emilk emilk Apr 22, 2025

Choose a reason for hiding this comment

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

This needs better docstring.

Does the struct describe the bounds of the safe area? If so, why isn't it a Rect?

Or is this describing the outer margins by which you need to shrink in order to make the screen rect safe? If so, SafeArea is a VERY confusing name (since it is really describing the unsafe margins). ScreenMargins would be a better name. And it can be (or wrap) a MarginF32

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I agree that the name isn't great, but it is called safe area (inset) in winit, in iOS and the web, so I don't think we should use a different term.

I'll rename it to SafeAreaInsets though and make it wrap MarginF32 (which is of course basically the same thing)

Comment on lines 451 to +470
pub fn screen_rect(&self) -> Rect {
self.screen_rect - self.safe_area
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

That self.screen_rect != self.screen_rect() is too confusing. Use different words for different things please

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think there are three options but none are great:

  1. keep screen_rect and have it return the "safe area rect"

    • needs some new name for the rect that includes the "unsafe area"
      • which is hard because the screen is the most outer rect of a computer. Maybe outer_screen_rect? 😕
    • no breaking changes for egui apps, since you'd want to use screen_rect in most places
  2. deprecate screen_rect and introduce two new terms

    • e.g. outer_rect and inner_rect or viewport_rect and content_rect
  3. keep screen_rect, having it return the "unsafe outer rect"

    • inner rect could be content_rect or inner_rect
    • naming would make sense
    • no breaking changes but most existing code using screen_rect would probably be wrong without any warning, since it should be using the screen_rect when it should be using content_rect

I think I prefer option 2 since it seems like the most "correct" thing to do

@emilk emilk marked this pull request as draft April 22, 2025 17:01
irh added a commit to irh/egui that referenced this pull request Jun 9, 2025
irh added a commit to irh/egui that referenced this pull request Jul 8, 2025
…4915)

This is a squashed version of the PR, rebased on to a more recent
upstream.
irh added a commit to irh/egui that referenced this pull request Sep 24, 2025
…4915)

This is a squashed version of the PR started by @frederik-uni and
continued by @lucasmerlin, rebased on to a more recent upstream.

emilk#4915
@irh
Copy link
Copy Markdown
Contributor

irh commented Sep 24, 2025

@lucasmerlin I've gone ahead and tried to address the open points of feedback on a branch here: https://github.com/irh/egui/tree/ios-safe-area.

It would be great if we could get this landed, should I open a new PR or would you prefer to pull my changes into this one?

@lucasmerlin
Copy link
Copy Markdown
Collaborator

@irh Nice! You should create a new one, I don't think you can update this one

irh pushed a commit to irh/egui that referenced this pull request Oct 2, 2025
…4915)

Co-authored-by: lucasmerlin <[email protected]>

This is a squashed version of the PR started by @frederik-uni and
continued by @lucasmerlin, rebased on to a more recent `master`.

emilk#4915
@irh
Copy link
Copy Markdown
Contributor

irh commented Oct 2, 2025

@irh Nice! You should create a new one, I don't think you can update this one

@lucasmerlin Ok great, new PR here: #7578.

irh pushed a commit to irh/egui that referenced this pull request Oct 6, 2025
…4915)

Co-authored-by: lucasmerlin <[email protected]>

This is a squashed version of the PR started by @frederik-uni and
continued by @lucasmerlin, rebased on to a more recent `master`.

emilk#4915
@lucasmerlin lucasmerlin closed this Oct 6, 2025
lucasmerlin added a commit that referenced this pull request Oct 7, 2025
This PR is a continuation of #4915 by @frederik-uni and @lucasmerlin
that introduces support for keeping egui content within the 'safe area'
on iOS (avoiding the notch / dynamic island / menu bar etc.), with the
following changes:

- `SafeArea` now wraps `MarginF32` and has been renamed to
`SafeAreaInsets` to clarify its purpose.
- `InputState::screen_rect` is now marked as deprecated in favour of
either `viewport_rect` (which contains the entire screen), or
`content_rect` (which is the viewport rect with the safe area insets
removed).
- I added some comments to the safe area insets logic pointing out the
[safe area API coming in winit
v0.31](rust-windowing/winit#3910).

---------

Co-authored-by: frederik-uni <[email protected]>
Co-authored-by: Lucas Meurer <[email protected]>
Co-authored-by: Emil Ernerfeldt <[email protected]>
Masterchef365 pushed a commit to Masterchef365/egui that referenced this pull request Apr 3, 2026
This PR is a continuation of emilk#4915 by @frederik-uni and @lucasmerlin
that introduces support for keeping egui content within the 'safe area'
on iOS (avoiding the notch / dynamic island / menu bar etc.), with the
following changes:

- `SafeArea` now wraps `MarginF32` and has been renamed to
`SafeAreaInsets` to clarify its purpose.
- `InputState::screen_rect` is now marked as deprecated in favour of
either `viewport_rect` (which contains the entire screen), or
`content_rect` (which is the viewport rect with the safe area insets
removed).
- I added some comments to the safe area insets logic pointing out the
[safe area API coming in winit
v0.31](rust-windowing/winit#3910).

---------

Co-authored-by: frederik-uni <[email protected]>
Co-authored-by: Lucas Meurer <[email protected]>
Co-authored-by: Emil Ernerfeldt <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

eframe Relates to epi and eframe egui feature New feature or request iOS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants