use cocoa::appkit::{
NSViewHeightSizable, NSViewWidthSizable, NSVisualEffectBlendingMode, NSVisualEffectMaterial,
NSVisualEffectState,
};
use cocoa::base::{id, nil, NO, YES};
use cocoa::foundation::NSRect;
use objc::runtime::{Class, Object, Sel, BOOL};
use objc::{class, msg_send, sel, sel_impl};
use super::registry::ViewHandle;
use super::utils::glass_class_available;
use crate::error::{Error, Result};
fn autoresize_mask() -> u64 {
NSViewWidthSizable | NSViewHeightSizable
}
pub trait GlassBackend {
unsafe fn create_view(&self, bounds: NSRect) -> Result<id>;
unsafe fn apply_tint(
&self,
view: id,
layer: id,
color: id,
existing_overlay: Option<ViewHandle>,
) -> Option<ViewHandle>;
unsafe fn clear_tint(&self, view: id, existing_overlay: Option<ViewHandle>);
unsafe fn set_variant(&self, view: id, variant: i64);
}
struct NativeGlassBackend;
impl GlassBackend for NativeGlassBackend {
unsafe fn create_view(&self, bounds: NSRect) -> Result<id> {
let glass_class = Class::get("NSGlassEffectView").ok_or(Error::ViewCreationFailed)?;
let glass: id = msg_send![glass_class, alloc];
let glass: id = msg_send![glass, initWithFrame: bounds];
let _: () = msg_send![glass, setAutoresizingMask: autoresize_mask()];
Ok(glass)
}
unsafe fn apply_tint(
&self,
view: id,
_layer: id,
color: id,
_existing_overlay: Option<ViewHandle>,
) -> Option<ViewHandle> {
let _: () = msg_send![view, setTintColor: color];
None
}
unsafe fn clear_tint(&self, view: id, _existing_overlay: Option<ViewHandle>) {
let _: () = msg_send![view, setTintColor: nil];
}
unsafe fn set_variant(&self, view: id, variant: i64) {
set_view_property(view, "variant", variant);
}
}
struct VisualEffectBackend;
impl GlassBackend for VisualEffectBackend {
unsafe fn create_view(&self, bounds: NSRect) -> Result<id> {
let visual: id = msg_send![class!(NSVisualEffectView), alloc];
let visual: id = msg_send![visual, initWithFrame: bounds];
let _: () = msg_send![visual, setAutoresizingMask: autoresize_mask()];
let _: () = msg_send![visual, setBlendingMode: NSVisualEffectBlendingMode::BehindWindow];
let _: () = msg_send![visual, setMaterial: NSVisualEffectMaterial::UnderWindowBackground];
let _: () = msg_send![visual, setState: NSVisualEffectState::Active];
Ok(visual)
}
unsafe fn apply_tint(
&self,
view: id,
layer: id,
color: id,
existing_overlay: Option<ViewHandle>,
) -> Option<ViewHandle> {
let overlay: id = if let Some(handle) = existing_overlay {
handle.as_id()
} else {
let bounds: NSRect = msg_send![view, bounds];
let overlay: id = msg_send![class!(NSView), alloc];
let overlay: id = msg_send![overlay, initWithFrame: bounds];
let _: () = msg_send![overlay, setAutoresizingMask: autoresize_mask()];
let _: () = msg_send![overlay, setWantsLayer: YES];
let _: () = msg_send![view, addSubview: overlay];
overlay
};
let overlay_layer: id = msg_send![overlay, layer];
if overlay_layer != nil {
let cg_color: id = msg_send![color, CGColor];
let _: () = msg_send![overlay_layer, setBackgroundColor: cg_color];
if layer != nil {
let radius: f64 = msg_send![layer, cornerRadius];
let _: () = msg_send![overlay_layer, setCornerRadius: radius];
let _: () = msg_send![overlay_layer, setMasksToBounds: YES];
}
}
Some(ViewHandle::new(overlay))
}
unsafe fn clear_tint(&self, _view: id, existing_overlay: Option<ViewHandle>) {
if let Some(handle) = existing_overlay {
let overlay = handle.as_id();
let _: () = msg_send![overlay, removeFromSuperview];
}
}
unsafe fn set_variant(&self, _view: id, _variant: i64) {
}
}
pub fn get_backend() -> Box<dyn GlassBackend> {
if glass_class_available() {
Box::new(NativeGlassBackend)
} else {
Box::new(VisualEffectBackend)
}
}
unsafe fn set_view_property(view: id, key: &str, value: i64) {
let obj = view;
let private_sel = Sel::register(&format!("set_{}:", key));
if try_send_i64(obj, private_sel, value) {
return;
}
let public_sel = Sel::register(&format!(
"set{}{}:",
key.chars().next().unwrap().to_uppercase(),
&key[1..]
));
try_send_i64(obj, public_sel, value);
}
unsafe fn try_send_i64(obj: *mut Object, sel: Sel, value: i64) -> bool {
let responds: BOOL = msg_send![obj, respondsToSelector: sel];
if responds != NO {
let _: () = objc::__send_message(&*obj, sel, (value,)).unwrap_or(());
true
} else {
false
}
}