Skip to content

Commit

Permalink
feat: Add traffic light inset (#1446)
Browse files Browse the repository at this point in the history
* feat: Add traffic light inset

* fix?

* ios

* ios

* don't set inset in new()

* guard Cell behind cfg macos

* usage warnings
  • Loading branch information
FabianLars authored Jan 7, 2025
1 parent 2ffcd49 commit cbbcccc
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changes/traffic-light-inset.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
wry: patch
---

Add functionality to set the traffic light inset on macOS. This is required to prevent flickers if the WebView is injected via `build()` instead of `build_as_child()`.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ objc2-ui-kit = { version = "0.2.2", features = [
[target."cfg(target_os = \"macos\")".dependencies]
objc2-app-kit = { version = "0.2.0", features = [
"NSApplication",
"NSButton",
"NSControl",
"NSEvent",
"NSWindow",
"NSView",
Expand Down
24 changes: 24 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,7 @@ impl<'a> WebViewBuilder<'a> {
#[derive(Clone, Default)]
pub(crate) struct PlatformSpecificWebViewAttributes {
data_store_identifier: Option<[u8; 16]>,
traffic_light_inset: Option<dpi::Position>,
}

#[cfg(any(target_os = "macos", target_os = "ios",))]
Expand All @@ -1247,6 +1248,12 @@ pub trait WebViewBuilderExtDarwin {
///
/// - **macOS / iOS**: Available on macOS >= 14 and iOS >= 17
fn with_data_store_identifier(self, identifier: [u8; 16]) -> Self;
/// Move the window controls to the specified position.
/// Normally this is handled by the Window but because `WebViewBuilder::build()` overwrites the window's NSView the controls will flicker on resizing.
/// Note: This method has no effects if the WebView is injected via `WebViewBuilder::build_as_child();` and there should be no flickers.
/// Warning: Do not use this if your chosen window library does not support traffic light insets.
/// Warning: Only use this in **decorated** windows with a **hidden titlebar**!
fn with_traffic_light_inset<P: Into<dpi::Position>>(self, position: P) -> Self;
}

#[cfg(any(target_os = "macos", target_os = "ios",))]
Expand All @@ -1257,6 +1264,13 @@ impl WebViewBuilderExtDarwin for WebViewBuilder<'_> {
Ok(b)
})
}

fn with_traffic_light_inset<P: Into<dpi::Position>>(self, position: P) -> Self {
self.and_then(|mut b| {
b.platform_specific.traffic_light_inset = Some(position.into());
Ok(b)
})
}
}

#[cfg(windows)]
Expand Down Expand Up @@ -1915,6 +1929,12 @@ pub trait WebViewExtMacOS {
fn reparent(&self, window: *mut NSWindow) -> Result<()>;
// Prints with extra options
fn print_with_options(&self, options: &PrintOptions) -> Result<()>;
/// Move the window controls to the specified position.
/// Normally this is handled by the Window but because `WebViewBuilder::build()` overwrites the window's NSView the controls will flicker on resizing.
/// Note: This method has no effects if the WebView is injected via `WebViewBuilder::build_as_child();` and there should be no flickers.
/// Warning: Do not use this if your chosen window library does not support traffic light insets.
/// Warning: Only use this in **decorated** windows with a **hidden titlebar**!
fn set_traffic_light_inset<P: Into<dpi::Position>>(&self, position: P) -> Result<()>;
}

#[cfg(target_os = "macos")]
Expand All @@ -1938,6 +1958,10 @@ impl WebViewExtMacOS for WebView {
fn print_with_options(&self, options: &PrintOptions) -> Result<()> {
self.webview.print_with_options(options)
}

fn set_traffic_light_inset<P: Into<dpi::Position>>(&self, position: P) -> Result<()> {
self.webview.set_traffic_light_inset(position.into())
}
}

/// Additional methods on `WebView` that are specific to iOS.
Expand Down
69 changes: 66 additions & 3 deletions src/wkwebview/class/wry_web_view_parent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use std::cell::Cell;

use objc2::{
declare_class, msg_send_id, mutability::MainThreadOnly, rc::Retained, ClassType, DeclaredClass,
};
#[cfg(target_os = "macos")]
use objc2_app_kit::{NSApplication, NSEvent, NSView};
use objc2_app_kit::{NSApplication, NSEvent, NSView, NSWindow, NSWindowButton};
use objc2_foundation::MainThreadMarker;
#[cfg(target_os = "macos")]
use objc2_foundation::NSRect;
#[cfg(target_os = "ios")]
use objc2_ui_kit::UIView as NSView;

pub struct WryWebViewParentIvars {}
pub struct WryWebViewParentIvars {
#[cfg(target_os = "macos")]
traffic_light_inset: Cell<Option<(f64, f64)>>,
}

declare_class!(
pub struct WryWebViewParent;
Expand Down Expand Up @@ -41,6 +48,14 @@ declare_class!(
}
}
}

#[cfg(target_os = "macos")]
#[method(drawRect:)]
fn draw(&self, _dirty_rect: NSRect) {
if let Some((x, y)) = self.ivars().traffic_light_inset.get() {
unsafe {inset_traffic_lights(&self.window().unwrap(), x, y)};
}
}
}
);

Expand All @@ -49,7 +64,55 @@ impl WryWebViewParent {
pub fn new(mtm: MainThreadMarker) -> Retained<Self> {
let delegate = mtm
.alloc::<WryWebViewParent>()
.set_ivars(WryWebViewParentIvars {});
.set_ivars(WryWebViewParentIvars {
#[cfg(target_os = "macos")]
traffic_light_inset: Default::default(),
});
unsafe { msg_send_id![super(delegate), init] }
}

#[cfg(target_os = "macos")]
pub fn set_traffic_light_inset(&self, ns_window: &NSWindow, position: dpi::Position) {
let scale_factor = NSWindow::backingScaleFactor(ns_window);
let position = position.to_logical(scale_factor);
self
.ivars()
.traffic_light_inset
.replace(Some((position.x, position.y)));

unsafe {
inset_traffic_lights(ns_window, position.x, position.y);
}
}
}

#[cfg(target_os = "macos")]
pub unsafe fn inset_traffic_lights(window: &NSWindow, x: f64, y: f64) {
let close = window
.standardWindowButton(NSWindowButton::NSWindowCloseButton)
.unwrap();
let miniaturize = window
.standardWindowButton(NSWindowButton::NSWindowMiniaturizeButton)
.unwrap();
let zoom = window
.standardWindowButton(NSWindowButton::NSWindowZoomButton)
.unwrap();

let title_bar_container_view = close.superview().unwrap().superview().unwrap();

let close_rect = NSView::frame(&close);
let title_bar_frame_height = close_rect.size.height + y;
let mut title_bar_rect = NSView::frame(&title_bar_container_view);
title_bar_rect.size.height = title_bar_frame_height;
title_bar_rect.origin.y = window.frame().size.height - title_bar_frame_height;
title_bar_container_view.setFrame(title_bar_rect);

let space_between = NSView::frame(&miniaturize).origin.x - close_rect.origin.x;
let window_buttons = vec![close, miniaturize, zoom];

for (i, button) in window_buttons.into_iter().enumerate() {
let mut rect = NSView::frame(&button);
rect.origin.x = x + (i as f64 * space_between);
button.setFrameOrigin(rect.origin);
}
}
30 changes: 26 additions & 4 deletions src/wkwebview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ pub(crate) struct InnerWebView {
// We need this the keep the reference count
ui_delegate: Retained<WryWebViewUIDelegate>,
protocol_ptrs: Vec<*mut Box<dyn Fn(crate::WebViewId, Request<Vec<u8>>, RequestAsyncResponder)>>,
#[cfg(target_os = "macos")]
// We need this to update the traffic light inset
parent_view: Option<Retained<WryWebViewParent>>,
}

impl InnerWebView {
Expand Down Expand Up @@ -459,7 +462,7 @@ impl InnerWebView {
}
}

let w = Self {
let mut w = Self {
id: webview_id,
webview: webview.clone(),
manager: manager.clone(),
Expand All @@ -473,6 +476,8 @@ impl InnerWebView {
ui_delegate,
protocol_ptrs,
is_child,
#[cfg(target_os = "macos")]
parent_view: None,
};

// Initialize scripts
Expand Down Expand Up @@ -504,19 +509,27 @@ r#"Object.defineProperty(window, 'ipc', {
if is_child {
ns_view.addSubview(&webview);
} else {
// inject the webview into the window
let ns_window = ns_view.window().unwrap();

let parent_view = WryWebViewParent::new(mtm);

if let Some(position) = pl_attrs.traffic_light_inset {
parent_view.set_traffic_light_inset(&ns_window, position);
}

parent_view.setAutoresizingMask(
NSAutoresizingMaskOptions::NSViewHeightSizable
| NSAutoresizingMaskOptions::NSViewWidthSizable,
);
parent_view.addSubview(&webview.clone());

// inject the webview into the window
let ns_window = ns_view.window().unwrap();
// Tell the webview receive keyboard events in the window.
// See https://github.com/tauri-apps/wry/issues/739
ns_window.setContentView(Some(&parent_view));
ns_window.makeFirstResponder(Some(&webview));

w.parent_view = Some(parent_view);
}

// make sure the window is always on top when we create a new webview
Expand Down Expand Up @@ -884,7 +897,7 @@ r#"Object.defineProperty(window, 'ipc', {
(secure && url.scheme() == "https") ||
// or cookie is secure and is localhost
(
secure && url.scheme() == "http" &&
secure && url.scheme() == "http" &&
(url.domain() == Some("localhost") || url.domain().and_then(|d| Ipv4Addr::from_str(d).ok()).map(|ip| ip.is_loopback()).unwrap_or(false))
) ||
// or cookie is not secure
Expand Down Expand Up @@ -926,6 +939,15 @@ r#"Object.defineProperty(window, 'ipc', {

Ok(())
}

#[cfg(target_os = "macos")]
pub(crate) fn set_traffic_light_inset(&self, position: dpi::Position) -> crate::Result<()> {
if let Some(parent_view) = &self.parent_view {
parent_view.set_traffic_light_inset(&self.webview.window().unwrap(), position);
}

Ok(())
}
}

pub fn url_from_webview(webview: &WKWebView) -> Result<String> {
Expand Down

0 comments on commit cbbcccc

Please sign in to comment.