use cocoa::base::{id, nil, NO, YES};
use cocoa::foundation::NSRect;
use log::warn;
use objc::runtime::{Class, BOOL};
use objc::{class, msg_send, sel, sel_impl};
use tauri::{AppHandle, Manager, Runtime, WebviewWindow};
use super::backend::get_backend;
use super::registry::{GlassViewRegistry, ViewHandle};
use super::utils::{color_from_hex, run_on_main_sync};
use crate::error::{Error, Result};
use crate::models::LiquidGlassConfig;
const NS_WINDOW_BELOW: i64 = -1;
pub fn create_glass_effect<R: Runtime>(
app: &AppHandle<R>,
window: &WebviewWindow<R>,
config: &LiquidGlassConfig,
) -> Result<()> {
let registry = app.state::<GlassViewRegistry>();
let window_label = window.label().to_string();
let ns_window = window
.ns_window()
.map_err(|_| Error::WindowNotFound(window_label.clone()))?;
let ns_window_handle = ViewHandle::new(ns_window as id);
let config = config.clone();
let (glass_view, tint_overlay) = run_on_main_sync(move || unsafe {
create_and_attach_glass_view(ns_window_handle, &config)
})?;
registry.insert(window_label, glass_view, tint_overlay)?;
Ok(())
}
pub fn update_glass_effect<R: Runtime>(
app: &AppHandle<R>,
window: &WebviewWindow<R>,
config: &LiquidGlassConfig,
) -> Result<()> {
let registry = app.state::<GlassViewRegistry>();
let window_label = window.label().to_string();
let (glass_handle, existing_tint) = registry
.get(&window_label)?
.ok_or_else(|| Error::WindowNotFound(window_label.clone()))?;
let config = config.clone();
let new_tint = run_on_main_sync(move || unsafe {
apply_glass_config(glass_handle, &config, existing_tint)
});
registry.update_tint(&window_label, new_tint)?;
Ok(())
}
pub fn remove_glass_effect<R: Runtime>(app: &AppHandle<R>, window_label: &str) -> Result<()> {
let registry = app.state::<GlassViewRegistry>();
let entry = registry.remove(window_label)?;
if let Some((glass_handle, tint_handle)) = entry {
run_on_main_sync(move || unsafe {
if let Some(tint) = tint_handle {
let _: () = msg_send![tint.as_id(), removeFromSuperview];
}
let _: () = msg_send![glass_handle.as_id(), removeFromSuperview];
});
}
Ok(())
}
unsafe fn create_and_attach_glass_view(
ns_window_handle: ViewHandle,
config: &LiquidGlassConfig,
) -> Result<(ViewHandle, Option<ViewHandle>)> {
let ns_window = ns_window_handle.as_id();
let content_view: id = msg_send![ns_window, contentView];
if content_view == nil {
return Err(Error::ViewCreationFailed);
}
check_window_transparency(ns_window);
check_webview_transparency(content_view);
let bounds: NSRect = msg_send![content_view, bounds];
let backend = get_backend();
let glass_view = backend.create_view(bounds)?;
let glass_handle = ViewHandle::new(glass_view);
let tint_overlay = apply_glass_config(glass_handle, config, None);
let _: () =
msg_send![content_view, addSubview: glass_view positioned: NS_WINDOW_BELOW relativeTo: nil];
Ok((glass_handle, tint_overlay))
}
unsafe fn apply_glass_config(
glass_handle: ViewHandle,
config: &LiquidGlassConfig,
existing_tint_overlay: Option<ViewHandle>,
) -> Option<ViewHandle> {
let glass = glass_handle.as_id();
let _: () = msg_send![glass, setWantsLayer: YES];
let layer: id = msg_send![glass, layer];
if layer != nil {
let _: () = msg_send![layer, setCornerRadius: config.corner_radius];
let _: () = msg_send![layer, setMasksToBounds: YES];
}
let backend = get_backend();
let tint_overlay = if let Some(ref hex) = config.tint_color {
if let Some(color) = color_from_hex(hex) {
backend.apply_tint(glass, layer, color, existing_tint_overlay)
} else {
backend.clear_tint(glass, existing_tint_overlay);
None
}
} else {
backend.clear_tint(glass, existing_tint_overlay);
None
};
backend.set_variant(glass, config.variant as i64);
tint_overlay
}
unsafe fn check_window_transparency(ns_window: id) {
let is_opaque: BOOL = msg_send![ns_window, isOpaque];
if is_opaque != NO {
warn!(
"Window is opaque. For liquid glass effect to show through, \
set window transparency in tauri.conf.json or via window builder."
);
}
}
unsafe fn check_webview_transparency(content_view: id) {
if let Some(webview) = find_webview(content_view) {
let key: id =
msg_send![class!(NSString), stringWithUTF8String: c"drawsBackground".as_ptr()];
let draws_bg: id = msg_send![webview, valueForKey: key];
if draws_bg != nil {
let draws: BOOL = msg_send![draws_bg, boolValue];
if draws != NO {
warn!(
"WebView has background drawing enabled. For liquid glass effect to show through, \
set transparent background in your HTML/CSS (e.g., background: transparent)."
);
}
}
}
}
unsafe fn find_webview(view: id) -> Option<id> {
if view == nil {
return None;
}
if let Some(webview_class) = Class::get("WKWebView") {
let is_webview: BOOL = msg_send![view, isKindOfClass: webview_class];
if is_webview != NO {
return Some(view);
}
}
let subviews: id = msg_send![view, subviews];
let count: usize = msg_send![subviews, count];
for i in 0..count {
let subview: id = msg_send![subviews, objectAtIndex: i];
if let Some(webview) = find_webview(subview) {
return Some(webview);
}
}
None
}