Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 36 additions & 15 deletions winit-android/src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};

use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
use android_activity::{
AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect,
};
use android_activity::{AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent};
use dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use tracing::{debug, trace, warn};
use winit_core::application::ApplicationHandler;
Expand Down Expand Up @@ -178,6 +176,7 @@ impl EventLoop {
let cause = self.cause;
let mut pending_redraw = self.pending_redraw;
let mut resized = false;
let mut safe_area_changed = false;

app.new_events(&self.window_target, cause);

Expand All @@ -193,9 +192,7 @@ impl EventLoop {
},
MainEvent::WindowResized { .. } => resized = true,
MainEvent::RedrawNeeded { .. } => pending_redraw = true,
MainEvent::ContentRectChanged { .. } => {
warn!("TODO: find a way to notify application of content rect change");
},
MainEvent::ContentRectChanged { .. } => safe_area_changed = true,
MainEvent::GainedFocus => {
HAS_FOCUS.store(true, Ordering::Relaxed);
let event = event::WindowEvent::Focused(true);
Expand Down Expand Up @@ -286,18 +283,39 @@ impl EventLoop {
}

if self.running {
if resized {
let size = if let Some(native_window) = self.android_app.native_window().as_ref() {
fn get_window_size(app: &AndroidApp) -> PhysicalSize<u32> {
if let Some(native_window) = app.native_window().as_ref() {
let width = native_window.width() as _;
let height = native_window.height() as _;
PhysicalSize::new(width, height)
} else {
PhysicalSize::new(0, 0)
};
}
}

fn get_insets(app: &AndroidApp) -> PhysicalInsets<u32> {
let insets = app.content_rect();
let outer_size = get_window_size(app);
PhysicalInsets {
top: insets.top as u32,
left: insets.left as u32,
bottom: outer_size.height.saturating_sub(insets.bottom as u32),
right: outer_size.width.saturating_sub(insets.right as u32),
}
}

if resized {
let size = get_window_size(&self.android_app);
let event = event::WindowEvent::SurfaceResized(size);
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
}

if safe_area_changed {
let insets = get_insets(&self.android_app);
let event = event::WindowEvent::SafeAreaChanged(insets);
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
}

pending_redraw |= self.redraw_flag.get_and_reset();
if pending_redraw {
pending_redraw = false;
Expand Down Expand Up @@ -784,10 +802,6 @@ impl Window {
self.app.config()
}

pub(crate) fn content_rect(&self) -> Rect {
self.app.content_rect()
}

// Allow the usage of HasRawWindowHandle inside this function
#[allow(deprecated)]
fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
Expand Down Expand Up @@ -852,7 +866,7 @@ impl CoreWindow for Window {
fn pre_present_notify(&self) {}

fn surface_position(&self) -> PhysicalPosition<i32> {
(0, 0).into()
(0.0, 0.0).into()
}

fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
Expand All @@ -876,7 +890,14 @@ impl CoreWindow for Window {
}

fn safe_area(&self) -> PhysicalInsets<u32> {
PhysicalInsets::new(0, 0, 0, 0)
let insets = self.app.content_rect();
let outer_size = self.outer_size();
PhysicalInsets {
top: insets.top as u32,
left: insets.left as u32,
bottom: outer_size.height.saturating_sub(insets.bottom as u32),
right: outer_size.width.saturating_sub(insets.right as u32),
}
}

fn set_min_surface_size(&self, _: Option<Size>) {}
Expand Down
9 changes: 1 addition & 8 deletions winit-android/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ mod keycodes;
use winit_core::event_loop::ActiveEventLoop as CoreActiveEventLoop;
use winit_core::window::Window as CoreWindow;

use self::activity::{AndroidApp, ConfigurationRef, Rect};
use self::activity::{AndroidApp, ConfigurationRef};
pub use crate::event_loop::{
ActiveEventLoop, EventLoop, EventLoopProxy, PlatformSpecificEventLoopAttributes,
PlatformSpecificWindowAttributes, Window,
Expand All @@ -97,17 +97,10 @@ pub trait ActiveEventLoopExtAndroid {

/// Additional methods on [`Window`] that are specific to Android.
pub trait WindowExtAndroid {
fn content_rect(&self) -> Rect;

fn config(&self) -> ConfigurationRef;
}

impl WindowExtAndroid for dyn CoreWindow + '_ {
fn content_rect(&self) -> Rect {
let window = self.cast_ref::<Window>().unwrap();
window.content_rect()
}

fn config(&self) -> ConfigurationRef {
let window = self.cast_ref::<Window>().unwrap();
window.config()
Expand Down
17 changes: 16 additions & 1 deletion winit-core/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::f64;
use std::path::PathBuf;
use std::sync::{Mutex, Weak};

use dpi::{PhysicalPosition, PhysicalSize};
use dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
Expand Down Expand Up @@ -59,6 +59,21 @@ pub enum WindowEvent {
/// [`Window::surface_size`]: crate::window::Window::surface_size
SurfaceResized(PhysicalSize<u32>),

/// The safe area
///
/// Contains the new safe area insets (can also be retrieved with
/// [`Window::safe_area`]).
///
/// This event will not necessarily be emitted upon window creation, query
/// [`Window::safe_area`] if you need to determine the initial safe area.
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.

Should discuss the relationship with SurfaceResized (and specifically that either may be emitted independently).

Unless we don't want that, and think it makes sense to also emit a SurfaceResized? But if so, what is the point of SafeAreaChanged?

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.

Yes, probably. I guess if you get SurfaceResized, then you probably need to re-resolve the safe area (as it's insets relative to the surface size), but the other way round is not true. You can process the safe area events assuming that the surface size is unchanged.

///
/// ## Platform-specific
///
/// - **macOS / Orbital / Wayland / Windows / X11:** Unsupported.
///
/// [`Window::safe_area`]: crate::window::Window::safe_area
SafeAreaChanged(PhysicalInsets<u32>),

/// The position of the window has changed.
///
/// Contains the window's new position in desktop coordinates (can also be retrieved with
Expand Down
2 changes: 1 addition & 1 deletion winit-core/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
///
/// ## Platform-specific
///
/// - **Android / Orbital / Wayland / Windows / X11:** Unimplemented, returns `(0, 0, 0, 0)`.
/// - **Orbital / Wayland / Windows / X11:** Unimplemented, returns `(0, 0, 0, 0)`.
///
/// ## Example
///
Expand Down
33 changes: 28 additions & 5 deletions winit-uikit/src/view.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::{Cell, RefCell};

use dpi::PhysicalPosition;
use dpi::{LogicalInsets, PhysicalInsets, PhysicalPosition};
use objc2::rc::Retained;
use objc2::runtime::{NSObjectProtocol, ProtocolObject};
use objc2::{DefinedClass, MainThreadMarker, available, define_class, msg_send, sel};
use objc2_core_foundation::{CGFloat, CGPoint, CGRect};
use objc2_foundation::{NSObject, NSSet, NSString};
use objc2_ui_kit::{
UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate,
UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer, UIPinchGestureRecognizer,
UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer, UITextInputTraits, UITouch,
UITouchPhase, UITouchType, UITraitEnvironment, UIView,
UIApplication, UIEdgeInsets, UIEvent, UIForceTouchCapability, UIGestureRecognizer,
UIGestureRecognizerDelegate, UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer,
UIPinchGestureRecognizer, UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer,
UITextInputTraits, UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView,
};
use tracing::debug;
use winit_core::event::{
Expand Down Expand Up @@ -127,6 +127,15 @@ define_class!(
debug!("safeAreaInsetsDidChange was called, requesting redraw");
// When the safe area changes we want to make sure to emit a redraw event
self.setNeedsDisplay();
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.

Should be updated depending on what we decide the semantics should be.


let window = self.window().unwrap();
let event = EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::SafeAreaChanged(self.safe_area()),
};

let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, event);
}

#[unsafe(method(touchesBegan:withEvent:))]
Expand Down Expand Up @@ -368,6 +377,20 @@ impl WinitView {
(**self).window().map(|window| window.downcast().unwrap())
}

pub(crate) fn safe_area(&self) -> PhysicalInsets<u32> {
let insets = if available!(ios = 11.0, tvos = 11.0, visionos = 1.0) {
self.safeAreaInsets()
} else {
// Assume the status bar frame is the only thing that obscures the view
let app = UIApplication::sharedApplication(MainThreadMarker::new().unwrap());
#[allow(deprecated)]
let status_bar_frame = app.statusBarFrame();
UIEdgeInsets { top: status_bar_frame.size.height, left: 0.0, bottom: 0.0, right: 0.0 }
};
let insets = LogicalInsets::new(insets.top, insets.left, insets.bottom, insets.right);
insets.to_physical(self.contentScaleFactor() as f64)
}

pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) {
let mtm = MainThreadMarker::from(self);
if should_recognize {
Expand Down
21 changes: 5 additions & 16 deletions winit-uikit/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@ use std::sync::{Arc, Mutex};

use dispatch2::MainThreadBound;
use dpi::{
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
Position, Size,
LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size,
};
use objc2::rc::Retained;
use objc2::{MainThreadMarker, available, class, define_class, msg_send};
use objc2::{MainThreadMarker, class, define_class, msg_send};
use objc2_core_foundation::{CGFloat, CGPoint, CGRect, CGSize};
use objc2_foundation::{NSObject, NSObjectProtocol};
use objc2_ui_kit::{
UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen,
UIScreenOverscanCompensation, UIViewController, UIWindow,
UICoordinateSpace, UIResponder, UIScreen, UIScreenOverscanCompensation, UIViewController,
UIWindow,
};
use tracing::{debug, warn};
use winit_core::cursor::Cursor;
Expand Down Expand Up @@ -202,17 +201,7 @@ impl Inner {
}

pub fn safe_area(&self) -> PhysicalInsets<u32> {
let insets = if available!(ios = 11.0, tvos = 11.0, visionos = 1.0) {
self.view.safeAreaInsets()
} else {
// Assume the status bar frame is the only thing that obscures the view
let app = UIApplication::sharedApplication(MainThreadMarker::new().unwrap());
#[allow(deprecated)]
let status_bar_frame = app.statusBarFrame();
UIEdgeInsets { top: status_bar_frame.size.height, left: 0.0, bottom: 0.0, right: 0.0 }
};
let insets = LogicalInsets::new(insets.top, insets.left, insets.bottom, insets.right);
insets.to_physical(self.scale_factor())
self.view.safe_area()
}

pub fn set_min_surface_size(&self, _dimensions: Option<Size>) {
Expand Down
6 changes: 6 additions & 0 deletions winit/examples/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,12 @@ impl ApplicationHandler for Application {
WindowEvent::SurfaceResized(size) => {
window.resize(size);
},
WindowEvent::SafeAreaChanged(insets) => {
info!("SafeAreaInsets={insets:?}");
// Do nothing as in this example we requery the safe area every frame anyway
// You could use this event to cache the safe area, although you also need to
// query the safe area at startup, and in response to SurfaceResize events.
},
WindowEvent::Focused(focused) => {
if focused {
info!("Window={window_id:?} focused");
Expand Down
2 changes: 2 additions & 0 deletions winit/src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ changelog entry.

- Add `keyboard` support for OpenHarmony.
- On iOS, add Apple Pencil support with force, altitude, and azimuth data.
- On Android, implement `Window::safe_area`. This replaces `WindowExtAndroid::content_rect` which has been removed.
- On iOS and Android, add a `WindowEvent::SafeAreaChanged` that notifies the user when the safe area changes.

### Changed

Expand Down
Loading