More cleanup
This commit is contained in:
@@ -91,6 +91,13 @@ struct MacosWindowState {
|
||||
has_presented_frame: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum PresentationFlushPolicy {
|
||||
None,
|
||||
DisplayIfNeeded,
|
||||
DisplayAndTransaction,
|
||||
}
|
||||
|
||||
impl MacosWindowState {
|
||||
fn new(desired_visible: bool) -> Self {
|
||||
Self {
|
||||
@@ -142,6 +149,10 @@ impl MacosWindowState {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn note_delegate_close_request(&mut self) -> bool {
|
||||
!self.close_in_progress && self.mark_close_requested_sent()
|
||||
}
|
||||
}
|
||||
|
||||
fn lifecycle_for_visibility(visible: bool) -> WindowLifecycle {
|
||||
@@ -152,69 +163,124 @@ fn lifecycle_for_visibility(visible: bool) -> WindowLifecycle {
|
||||
}
|
||||
}
|
||||
|
||||
struct ObjcStrong {
|
||||
object: Option<NonNull<AnyObject>>,
|
||||
}
|
||||
|
||||
impl ObjcStrong {
|
||||
unsafe fn from_owned(object: *mut AnyObject) -> Option<Self> {
|
||||
Some(Self {
|
||||
object: Some(NonNull::new(object)?),
|
||||
})
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *mut AnyObject {
|
||||
self.object
|
||||
.map_or(std::ptr::null_mut(), |object| object.as_ptr())
|
||||
}
|
||||
|
||||
fn release_now(&mut self) {
|
||||
let Some(object) = self.object.take() else {
|
||||
return;
|
||||
};
|
||||
unsafe {
|
||||
let _: () = objc2::msg_send![object.as_ptr(), release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ObjcStrong {
|
||||
fn drop(&mut self) {
|
||||
self.release_now();
|
||||
}
|
||||
}
|
||||
|
||||
struct NativeWindow {
|
||||
window: *mut AnyObject,
|
||||
window: ObjcStrong,
|
||||
#[allow(dead_code)]
|
||||
view: *mut AnyObject,
|
||||
delegate: *mut AnyObject,
|
||||
delegate: Option<ObjcStrong>,
|
||||
_metal_layer: Retained<CAMetalLayer>,
|
||||
renderer: Option<WgpuSceneRenderer>,
|
||||
}
|
||||
|
||||
impl NativeWindow {
|
||||
fn window_ptr(&self) -> usize {
|
||||
self.window as usize
|
||||
self.window.as_ptr() as usize
|
||||
}
|
||||
|
||||
fn window_object(&self) -> *mut AnyObject {
|
||||
self.window.as_ptr()
|
||||
}
|
||||
|
||||
fn view_object(&self) -> *mut AnyObject {
|
||||
self.view
|
||||
}
|
||||
|
||||
fn set_title(&self, title: &str) {
|
||||
let title = NSString::from_str(title);
|
||||
unsafe {
|
||||
let _: () = objc2::msg_send![self.window, setTitle: &*title];
|
||||
let _: () = objc2::msg_send![self.window_object(), setTitle: &*title];
|
||||
}
|
||||
}
|
||||
|
||||
fn set_content_size(&self, size: UiSize) {
|
||||
let content_size = CGSize::new(size.width as f64, size.height as f64);
|
||||
unsafe {
|
||||
let _: () = objc2::msg_send![self.window, setContentSize: content_size];
|
||||
let _: () = objc2::msg_send![self.window_object(), setContentSize: content_size];
|
||||
}
|
||||
}
|
||||
|
||||
fn show(&self) {
|
||||
unsafe {
|
||||
let _: () = objc2::msg_send![self.window, makeKeyAndOrderFront: std::ptr::null_mut::<AnyObject>()];
|
||||
let _: () = objc2::msg_send![self.window_object(), makeKeyAndOrderFront: std::ptr::null_mut::<AnyObject>()];
|
||||
}
|
||||
}
|
||||
|
||||
fn hide(&self) {
|
||||
unsafe {
|
||||
let _: () = objc2::msg_send![self.window, orderOut: std::ptr::null_mut::<AnyObject>()];
|
||||
let _: () =
|
||||
objc2::msg_send![self.window_object(), orderOut: std::ptr::null_mut::<AnyObject>()];
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&self) {
|
||||
unsafe {
|
||||
let _: () = objc2::msg_send![self.window, close];
|
||||
let _: () = objc2::msg_send![self.window_object(), close];
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_display(&self) -> bool {
|
||||
let in_live_resize: bool = unsafe { objc2::msg_send![self.view, inLiveResize] };
|
||||
fn in_live_resize(&self) -> bool {
|
||||
unsafe { objc2::msg_send![self.view, inLiveResize] }
|
||||
}
|
||||
|
||||
fn display_if_needed(&self) {
|
||||
unsafe {
|
||||
let _: () = objc2::msg_send![self.view, displayIfNeeded];
|
||||
let _: () = objc2::msg_send![self.window, displayIfNeeded];
|
||||
let _: () = objc2::msg_send![objc2::class!(CATransaction), flush];
|
||||
let _: () = objc2::msg_send![self.window_object(), displayIfNeeded];
|
||||
}
|
||||
in_live_resize
|
||||
}
|
||||
|
||||
fn detach_delegate(&self) {
|
||||
fn flush_core_animation_transaction(&self) {
|
||||
unsafe {
|
||||
let _: () =
|
||||
objc2::msg_send![self.window, setDelegate: std::ptr::null_mut::<AnyObject>()];
|
||||
let _: () = objc2::msg_send![self.delegate, release];
|
||||
let _: () = objc2::msg_send![objc2::class!(CATransaction), flush];
|
||||
}
|
||||
}
|
||||
|
||||
fn detach_delegate(&mut self) {
|
||||
unsafe {
|
||||
let _: () = objc2::msg_send![self.window_object(), setDelegate: std::ptr::null_mut::<AnyObject>()];
|
||||
}
|
||||
if let Some(mut delegate) = self.delegate.take() {
|
||||
delegate.release_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NativeWindow {
|
||||
fn drop(&mut self) {
|
||||
self.detach_delegate();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -1176,10 +1242,6 @@ fn handle_update_window(
|
||||
window.state.request_logical_close();
|
||||
if window.native.is_some() {
|
||||
close_native_window(window);
|
||||
// Keep behavior aligned with other current platform backends:
|
||||
// treat a requested close as immediately closed for app shutdown.
|
||||
window.state.finalize_close();
|
||||
emit_closed = true;
|
||||
} else {
|
||||
window.state.finalize_close();
|
||||
emit_closed = true;
|
||||
@@ -1321,8 +1383,8 @@ fn try_present_scene(
|
||||
return None;
|
||||
}
|
||||
|
||||
window.state.note_presented();
|
||||
flush_presented_frame(window);
|
||||
window.state.note_presented();
|
||||
if let Some(next_resize) = finish_presented_resize(window, scene_viewport) {
|
||||
trace!(
|
||||
target: "ruin_ui_platform_macos::resize",
|
||||
@@ -1356,8 +1418,8 @@ fn repaint_retained_scene_for_resize(window_id: WindowId, window: &mut MacosWind
|
||||
"repainting retained scene into resized drawable"
|
||||
);
|
||||
if render_scene_to_current_drawable(window_id, window, &scene) {
|
||||
window.state.note_presented();
|
||||
flush_presented_frame(window);
|
||||
window.state.note_presented();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1370,6 +1432,7 @@ fn render_scene_to_current_drawable(
|
||||
let scene_viewport = scene.logical_size;
|
||||
let mut render_succeeded = false;
|
||||
if let Some(native) = window.native.as_mut() {
|
||||
let view = native.view_object();
|
||||
let Some(renderer) = native.renderer.as_mut() else {
|
||||
trace!(
|
||||
target: "ruin_ui_platform_macos::resize",
|
||||
@@ -1378,7 +1441,7 @@ fn render_scene_to_current_drawable(
|
||||
);
|
||||
return false;
|
||||
};
|
||||
configure_view_resize_policy(native.view);
|
||||
configure_view_resize_policy(view);
|
||||
renderer.set_scale_factor(window.configuration.scale_factor);
|
||||
let scale = window.configuration.scale_factor.max(1.0);
|
||||
let drawable_width = (current_viewport.width * scale).max(1.0) as u32;
|
||||
@@ -1474,9 +1537,8 @@ fn create_native_window(window: &MacosWindow, app: *mut AnyObject) -> Option<Nat
|
||||
defer: false
|
||||
]
|
||||
};
|
||||
if window_obj.is_null() {
|
||||
return None;
|
||||
}
|
||||
let window_owner = unsafe { ObjcStrong::from_owned(window_obj) }?;
|
||||
let window_obj = window_owner.as_ptr();
|
||||
|
||||
let title = NSString::from_str(&window.spec.title);
|
||||
unsafe {
|
||||
@@ -1488,14 +1550,12 @@ fn create_native_window(window: &MacosWindow, app: *mut AnyObject) -> Option<Nat
|
||||
let view_class = content_view_class();
|
||||
let view_obj: *mut AnyObject = unsafe { objc2::msg_send![view_class, alloc] };
|
||||
let view_obj: *mut AnyObject = unsafe { objc2::msg_send![view_obj, initWithFrame: rect] };
|
||||
if view_obj.is_null() {
|
||||
return None;
|
||||
}
|
||||
let mut view_owner = unsafe { ObjcStrong::from_owned(view_obj) }?;
|
||||
let view_obj = view_owner.as_ptr();
|
||||
let delegate_class = window_delegate_class();
|
||||
let delegate_obj: *mut AnyObject = unsafe { objc2::msg_send![delegate_class, new] };
|
||||
if delegate_obj.is_null() {
|
||||
return None;
|
||||
}
|
||||
let delegate_owner = unsafe { ObjcStrong::from_owned(delegate_obj) }?;
|
||||
let delegate_obj = delegate_owner.as_ptr();
|
||||
|
||||
unsafe {
|
||||
let _: () = objc2::msg_send![
|
||||
@@ -1511,6 +1571,9 @@ fn create_native_window(window: &MacosWindow, app: *mut AnyObject) -> Option<Nat
|
||||
objc2::msg_send![window_obj, makeKeyAndOrderFront: std::ptr::null_mut::<AnyObject>()];
|
||||
let _: () = objc2::msg_send![app, activateIgnoringOtherApps: true];
|
||||
}
|
||||
// NSWindow retains its content view. Drop our construction-time ownership so
|
||||
// the window is the single owner from here.
|
||||
view_owner.release_now();
|
||||
|
||||
let metal_layer = configure_metal_surface_layer(view_obj)?;
|
||||
|
||||
@@ -1529,9 +1592,9 @@ fn create_native_window(window: &MacosWindow, app: *mut AnyObject) -> Option<Nat
|
||||
}
|
||||
|
||||
Some(NativeWindow {
|
||||
window: window_obj,
|
||||
window: window_owner,
|
||||
view: view_obj,
|
||||
delegate: delegate_obj,
|
||||
delegate: Some(delegate_owner),
|
||||
_metal_layer: metal_layer,
|
||||
renderer,
|
||||
})
|
||||
@@ -1644,7 +1707,7 @@ fn apply_delegate_events(state: &mut MacosState, pending: &mut Vec<PlatformEvent
|
||||
}) else {
|
||||
continue;
|
||||
};
|
||||
if window.state.mark_close_requested_sent() {
|
||||
if window.state.note_delegate_close_request() {
|
||||
debug!(
|
||||
target: "ruin_ui_platform_macos::lifecycle",
|
||||
?window_id,
|
||||
@@ -1730,6 +1793,19 @@ fn finish_presented_resize(window: &mut MacosWindow, presented: UiSize) -> Optio
|
||||
window.resize_pipeline.note_presented_scene(presented)
|
||||
}
|
||||
|
||||
fn presentation_flush_policy(
|
||||
has_presented_frame: bool,
|
||||
in_live_resize: bool,
|
||||
) -> PresentationFlushPolicy {
|
||||
if in_live_resize {
|
||||
PresentationFlushPolicy::DisplayAndTransaction
|
||||
} else if !has_presented_frame {
|
||||
PresentationFlushPolicy::DisplayIfNeeded
|
||||
} else {
|
||||
PresentationFlushPolicy::None
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_metal_surface_layer(view: *mut AnyObject) -> Option<Retained<CAMetalLayer>> {
|
||||
let view_ptr = NonNull::new(view.cast::<c_void>())?;
|
||||
let layer = unsafe { MetalLayer::from_ns_view(view_ptr) };
|
||||
@@ -1744,11 +1820,21 @@ fn flush_presented_frame(window: &MacosWindow) {
|
||||
let Some(native) = window.native.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let in_live_resize = native.flush_display();
|
||||
let in_live_resize = native.in_live_resize();
|
||||
let policy = presentation_flush_policy(window.state.has_presented_frame, in_live_resize);
|
||||
match policy {
|
||||
PresentationFlushPolicy::None => {}
|
||||
PresentationFlushPolicy::DisplayIfNeeded => native.display_if_needed(),
|
||||
PresentationFlushPolicy::DisplayAndTransaction => {
|
||||
native.display_if_needed();
|
||||
native.flush_core_animation_transaction();
|
||||
}
|
||||
}
|
||||
trace!(
|
||||
target: "ruin_ui_platform_macos::resize",
|
||||
in_live_resize,
|
||||
"requested display after presented frame"
|
||||
?policy,
|
||||
"applied presentation flush policy after presented frame"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1797,7 +1883,7 @@ fn collect_window_state_events(
|
||||
let Some((window_ptr, view_ptr)) = window
|
||||
.native
|
||||
.as_ref()
|
||||
.map(|native| (native.window, native.view))
|
||||
.map(|native| (native.window_object(), native.view_object()))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -1938,7 +2024,7 @@ fn finalize_native_close(
|
||||
"finalizing native close"
|
||||
);
|
||||
let should_emit_closed = window.state.lifecycle != WindowLifecycle::LogicalClosed;
|
||||
if let Some(native) = window.native.take() {
|
||||
if let Some(mut native) = window.native.take() {
|
||||
native.detach_delegate();
|
||||
}
|
||||
|
||||
@@ -1965,7 +2051,7 @@ fn sync_window_metrics(window: &mut MacosWindow) {
|
||||
let Some((window_ptr, view_ptr)) = window
|
||||
.native
|
||||
.as_ref()
|
||||
.map(|native| (native.window, native.view))
|
||||
.map(|native| (native.window_object(), native.view_object()))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -1978,7 +2064,9 @@ fn sync_window_metrics(window: &mut MacosWindow) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{MacosWindowState, ResizePipeline};
|
||||
use super::{
|
||||
MacosWindowState, PresentationFlushPolicy, ResizePipeline, presentation_flush_policy,
|
||||
};
|
||||
use ruin_ui::{UiSize, WindowLifecycle};
|
||||
|
||||
#[test]
|
||||
@@ -2045,6 +2133,22 @@ mod tests {
|
||||
assert_eq!(pipeline.next_resize, Some(next));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn presentation_flush_policy_is_narrowed_to_first_present_and_live_resize() {
|
||||
assert_eq!(
|
||||
presentation_flush_policy(false, false),
|
||||
PresentationFlushPolicy::DisplayIfNeeded
|
||||
);
|
||||
assert_eq!(
|
||||
presentation_flush_policy(true, false),
|
||||
PresentationFlushPolicy::None
|
||||
);
|
||||
assert_eq!(
|
||||
presentation_flush_policy(true, true),
|
||||
PresentationFlushPolicy::DisplayAndTransaction
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_state_distinguishes_hide_from_close() {
|
||||
let mut state = MacosWindowState::new(true);
|
||||
@@ -2080,4 +2184,16 @@ mod tests {
|
||||
assert!(!state.close_in_progress);
|
||||
assert!(!state.has_presented_frame);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_state_app_requested_close_suppresses_close_requested_event() {
|
||||
let mut state = MacosWindowState::new(true);
|
||||
state.open_with_visibility(true);
|
||||
|
||||
state.request_logical_close();
|
||||
|
||||
assert!(state.close_in_progress);
|
||||
assert!(!state.note_delegate_close_request());
|
||||
assert!(!state.close_requested_sent);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user