Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blur-Behind / Glassy Windows implementation #568

Closed
wants to merge 11 commits into from
39 changes: 39 additions & 0 deletions examples/blur.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
extern crate winit;

fn main() {
let mut events_loop = winit::EventsLoop::new();

let window = winit::WindowBuilder::new()
.with_title("A blurry window!")
.with_blur(true)
.build(&events_loop)
.unwrap();

#[cfg(target_os = "macos")]
{
// On macOS the blur material is 'light' by default.
// Let's change it to a dark theme!
use winit::os::macos::{BlurMaterial, WindowExt};
unsafe { window.set_blur_material(BlurMaterial::Dark) };
}

events_loop.run_forever(|event| {
match event {
winit::Event::WindowEvent {
event: winit::WindowEvent::CloseRequested,
..
} => winit::ControlFlow::Break,
winit::Event::WindowEvent {
event: winit::WindowEvent::KeyboardInput {
input: winit::KeyboardInput {
virtual_keycode: Some(winit::VirtualKeyCode::Escape),
..
},
..
},
..
} => winit::ControlFlow::Break,
_ => winit::ControlFlow::Continue,
}
});
}
11 changes: 11 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,16 @@ pub struct WindowAttributes {
/// [iOS only] Enable multitouch,
/// see [multipleTouchEnabled](https://developer.apple.com/documentation/uikit/uiview/1622519-multipletouchenabled)
pub multitouch: bool,

/// Whether the window should have a blur effect.
///
/// Blur is similar to transparency in that both allow you to "look though" the window,
/// except that with blur the seethrough content has an effect applied that gives it a
/// milky or smeared look.
/// The exact look can be controlled per platform via traits like `WindowExt`.
///
/// The default is `false`.
pub blur: bool,
}

impl Default for WindowAttributes {
Expand All @@ -501,6 +511,7 @@ impl Default for WindowAttributes {
always_on_top: false,
window_icon: None,
multitouch: false,
blur: false,
}
}
}
62 changes: 62 additions & 0 deletions src/os/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ pub trait WindowExt {
///
/// The pointer will become invalid when the `Window` is destroyed.
fn get_nsview(&self) -> *mut c_void;

/// For windows created with the [blurred](WindowBuilder::with_blur) option,
/// this controls the appearance of the blur effect.
///
/// Marked as unsafe because depending on the version of macOS and the `BlurMaterial` variant passed,
/// this might cause a crash.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a lowest common denominator of supported enum variants? I don't think people will be comfortable using a method that can crash based on criteria that are only vaguely explained.

Copy link
Author

@haudan haudan Jun 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lowest common denominator appears to be AppearanceBased, Light, Dark, MediumLight and UltraDark (source), but those are now marked as deprecated. There are some marked as "modified", I think that means their underlying integer value has changed (validation needed), which would disqualify them. This is all quite problematic since this swift enum is our only source of truth and it appears Apple is comfortable changing the order of constants willy nilly - they only keep the swift code consistent, but apparently not the enum's binary representation.

Honestly I don't know what to do here. This is why I was originally unsure about the BlurMaterial enum in the first place.
The only alternative I see is exposing the raw i64 and removing the enum, but that won't solve the crash / macOS-version dependency problem either.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How impractical would it be for us to translate the values depending on the version of macOS?

Also, shouldn't it be isize instead of i64?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that would be too much work, definitely doable.

Yeah you could be right with isize over i64. The right size is whatever the size of the swift enum is. isize should be a good bet.

unsafe fn set_blur_material(&self, material: BlurMaterial);
}

impl WindowExt for Window {
Expand All @@ -28,6 +35,11 @@ impl WindowExt for Window {
fn get_nsview(&self) -> *mut c_void {
self.window.get_nsview()
}

#[inline]
unsafe fn set_blur_material(&self, material: BlurMaterial) {
self.window.set_blur_material(material);
}
}

/// Corresponds to `NSApplicationActivationPolicy`.
Expand Down Expand Up @@ -157,3 +169,53 @@ impl MonitorIdExt for MonitorId {
self.inner.get_nsscreen().map(|s| s as *mut c_void)
}
}

/// Enumeration of all possible blur materials for macOS. Applies to macOS SDK 10.10+.
///
/// Not all versions of macOS support all the variants listed here.
/// Check [Apple's documentation](https://developer.apple.com/documentation/appkit/nsvisualeffectview/material)
/// to find out what your target version supports.
/// The behaviour for using a material which is not supported depends how it is implemented in cocoa,
/// but will most likely cause a crash.
#[repr(i64)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these actually supposed to be i64, or are they an architecture-dependent size? I'd appreciate a comment including the NSWhatever name of the enum.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an i64 based on the current NSVisualEffectView.setMaterial method signature. The method takes a c-style enum, which is represented as an i64. I don't think this is set in stone but I doubt it will ever change.

I have added some valuable documentation in my new commit.

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum BlurMaterial {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this enum is problematic. I created it based on the documentation Apple updated a while ago, based on the upcoming SDK version for macOS Mojave. Most of these variants are not supported on High Sierra, and will crash the application when attempted to be used. I'm unsure how to go about this. Should we remove the enum entirely and make the set_blur_material method take an i64 instead? That would more or less remove the OS version dependency from winit.

/// A default material for the view’s effective appearance.
AppearanceBased = 0,
/// A material with a light effect.
Light = 1,
/// A material with a dark effect.
Dark = 2,
/// The material for a window’s titlebar.
Titlebar = 3,
/// The material used to indicate a selection.
Selection = 4,
/// The material for menus.
Menu = 5,
/// The material for the background of popover windows.
Popover = 6,
/// The material for the background of window sidebars.
Sidebar = 7,
/// A material with a medium-light effect.
MediumLight = 8,
/// A material with an ultra-dark effect.
UltraDark = 9,
/// The material for in-line header or footer views.
HeaderView = 10,
/// The material for the background of sheet windows.
Sheet = 11,
/// The material for the background of opaque windows.
WindowBackground = 12,
/// The material for the background of heads-up display (HUD) windows.
HudWindow = 13,
/// The material for the background of a full-screen modal interface.
FullScreenUi = 15,
/// The material for the background of a tool tip.
ToolTip = 17,
/// The material for the background of opaque content.
ContentBackground = 18,
/// The material for under a window's background.
UnderWindowBackground = 21,
/// The material for the area behind the pages of a document.
UnderPageBackground = 22,
}
30 changes: 24 additions & 6 deletions src/platform/macos/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use cocoa::foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger};
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Protocol, Sel, BOOL};

use {ElementState, Event, KeyboardInput, MouseButton, WindowEvent, WindowId};
use os::macos::BlurMaterial;
use {ElementState, Event, KeyboardInput, MouseButton, WindowAttributes, WindowEvent, WindowId};
use platform::platform::events_loop::{DEVICE_ID, event_mods, Shared, to_virtual_key_code};
use platform::platform::util;
use platform::platform::ffi::*;
Expand All @@ -27,7 +28,7 @@ struct ViewState {
last_insert: Option<String>,
}

pub fn new_view(window: id, shared: Weak<Shared>) -> IdRef {
pub fn new_view(window: id, shared: Weak<Shared>, win_attribs: &WindowAttributes) -> IdRef {
let state = ViewState {
window,
shared,
Expand All @@ -38,8 +39,14 @@ pub fn new_view(window: id, shared: Weak<Shared>) -> IdRef {
unsafe {
// This is free'd in `dealloc`
let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void;
let view: id = msg_send![VIEW_CLASS.0, alloc];
IdRef::new(msg_send![view, initWithWinit:state_ptr])
let view_class = if win_attribs.blur { VISUAL_EFFECT_VIEW_CLASS.0 } else { VIEW_CLASS.0 };
let view: id = msg_send![view_class, alloc];
let view: id = msg_send![view, initWithWinit:state_ptr];
if win_attribs.blur {
let _: () = msg_send![view, setMaterial:BlurMaterial::Light as i64];
let _: () = msg_send![view, setBlendingMode:0i64];
}
IdRef::new(view)
}
}

Expand All @@ -65,6 +72,18 @@ unsafe impl Sync for ViewClass {}
lazy_static! {
static ref VIEW_CLASS: ViewClass = unsafe {
let superclass = Class::get("NSView").unwrap();
let decl = common_view_decl(superclass);
ViewClass(decl.register())
};

static ref VISUAL_EFFECT_VIEW_CLASS: ViewClass = unsafe {
let superclass = Class::get("NSVisualEffectView").unwrap();
let decl = common_view_decl(superclass);
ViewClass(decl.register())
};
}

unsafe fn common_view_decl(superclass: &'static Class) -> ClassDecl {
let mut decl = ClassDecl::new("WinitView", superclass).unwrap();
decl.add_method(sel!(dealloc), dealloc as extern fn(&Object, Sel));
decl.add_method(
Expand Down Expand Up @@ -126,8 +145,7 @@ lazy_static! {
decl.add_ivar::<id>("markedText");
let protocol = Protocol::get("NSTextInputClient").unwrap();
decl.add_protocol(&protocol);
ViewClass(decl.register())
};
decl
}

extern fn dealloc(this: &Object, _sel: Sel) {
Expand Down
13 changes: 9 additions & 4 deletions src/platform/macos/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use {
WindowId,
};
use CreationError::OsError;
use os::macos::{ActivationPolicy, WindowExt};
use os::macos::{ActivationPolicy, BlurMaterial, WindowExt};
use platform::platform::{ffi, util};
use platform::platform::events_loop::{EventsLoop, Shared};
use platform::platform::view::{new_view, set_ime_spot};
Expand Down Expand Up @@ -582,6 +582,11 @@ impl WindowExt for Window2 {
fn get_nsview(&self) -> *mut c_void {
*self.view as *mut c_void
}

#[inline]
unsafe fn set_blur_material(&self, material: BlurMaterial) {
let _: () = msg_send![*self.view, setMaterial:material as i64];
}
}

impl Window2 {
Expand Down Expand Up @@ -618,7 +623,7 @@ impl Window2 {
return Err(OsError(format!("Couldn't create NSWindow")));
},
};
let view = match Window2::create_view(*window, Weak::clone(&shared)) {
let view = match Window2::create_view(*window, Weak::clone(&shared), &win_attribs) {
Some(view) => view,
None => {
let _: () = unsafe { msg_send![autoreleasepool, drain] };
Expand Down Expand Up @@ -843,9 +848,9 @@ impl Window2 {
}
}

fn create_view(window: id, shared: Weak<Shared>) -> Option<IdRef> {
fn create_view(window: id, shared: Weak<Shared>, win_attribs: &WindowAttributes) -> Option<IdRef> {
unsafe {
let view = new_view(window, shared);
let view = new_view(window, shared, win_attribs);
view.non_nil().map(|view| {
view.setWantsBestResolutionOpenGLSurface_(YES);
window.setContentView_(*view);
Expand Down
14 changes: 14 additions & 0 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,20 @@ impl WindowBuilder {
#[inline]
pub fn with_transparency(mut self, transparent: bool) -> WindowBuilder {
self.window.transparent = transparent;
if transparent {
self.window.blur = false;
}
self
}

/// Sets whether the background of the window should be blurred.
/// See the blur member on [WindowAttributes](::WindowAttributes) for more info.
#[inline]
pub fn with_blur(mut self, blur: bool) -> WindowBuilder {
self.window.blur = blur;
if blur {
self.window.transparent = false;
}
self
}

Expand Down