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};
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,
}
});
}
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,11 @@ 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 be blurry. This is similar to transparency.
Copy link
Member

Choose a reason for hiding this comment

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

"blurry" has a pretty strong negative connotation. "blurred" sounds more neutral, but that has associations with input focus... "the window should have a blur effect" probably sounds the coolest, and then the second sentence should go on to explain things more.

///
/// The default is `false`.
pub blur: bool,
}

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

/// Just for testing purposes.
Copy link
Author

Choose a reason for hiding this comment

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

Should probably change that comment.

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

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

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

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

#[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.

// Applies to MacOS Mojave.
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.

AppearanceBased = 0, // Deperecated
Copy link
Member

Choose a reason for hiding this comment

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

Since this enum is public, the deprecation notices should be doc comments above the variant.

Light = 1, // Deperecated
Dark = 2, // Deprecated
Titlebar = 3,
Selection = 4,
Menu = 5,
Popover = 6,
Sidebar = 7,
MediumLight = 8, // Deprecated
UltraDark = 9, // Deprecated
HeaderView = 10,
Sheet = 11,
WindowBackground = 12,
HudWindow = 13,
FullScreenUi = 15,
ToolTip = 17,
ContentBackground = 18,
UnderWindowBackground = 21,
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,11 +13,12 @@ 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 {ElementState, Event, KeyboardInput, MouseButton, WindowEvent, WindowId, WindowAttributes};
Copy link
Member

Choose a reason for hiding this comment

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

I've been trying to keep imports sorted alphabetically, so WindowAttributes should go before WindowEvent.

use platform::platform::events_loop::{DEVICE_ID, event_mods, Shared, to_virtual_key_code};
use platform::platform::util;
use platform::platform::ffi::*;
use platform::platform::window::{get_window_id, IdRef};
use os::macos::BlurMaterial;
Copy link
Member

Choose a reason for hiding this comment

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

likewise, os imports go above platform imports.

Though, I might be more comfortable with you defining the BlurMaterial within platform, as IIRC no other types define themselves in os.

Copy link
Author

Choose a reason for hiding this comment

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

I tried defining BlurMaterial inside platform, but that creates a problem with module visibility. os needs access to this enum, but platform is inaccessible from os.

I don't know if we even should keep the enum, see my other comments.


struct ViewState {
window: id,
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];
msg_send![view, initWithWinit:state_ptr];
Copy link
Member

Choose a reason for hiding this comment

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

Proper Obj-C style dictates that we rebind view to the return of the init method. While it's typically the same pointer, it's not idiomatic to assume that.

if win_attribs.blur {
msg_send![view, setMaterial: BlurMaterial::Light as i64];
Copy link
Member

Choose a reason for hiding this comment

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

You have to do let _: () = msg_send!, as otherwise segsegv/sigill will be triggered at runtime when the ! (never) type is stabilized.

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: 10 additions & 3 deletions src/platform/macos/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::ops::Deref;
use std::os::raw::c_void;
use std::sync::Weak;
use std::cell::{Cell, RefCell};
use os::macos::BlurMaterial;
Copy link
Member

Choose a reason for hiding this comment

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

I've been grouping imports into blocks, ordered:

  1. std
  2. Crates
  3. Internal

With a newline between each block.

(I know this isn't consistently applied throughout the repo yet, but we're getting there.)


use cocoa;
use cocoa::appkit::{
Expand Down Expand Up @@ -582,6 +583,12 @@ impl WindowExt for Window2 {
fn get_nsview(&self) -> *mut c_void {
*self.view as *mut c_void
}

#[inline]
fn set_blur_material(&self, material: BlurMaterial) {
let view = self.get_nsview() as *mut objc::runtime::Object;
unsafe { msg_send![view, setMaterial: material as i64]; }
Copy link
Member

Choose a reason for hiding this comment

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

In every other usage, we call msg_send! on *self.view. Doing that is much cleaner (and has less potential overhead) than calling get_nsview like this.

Additionally, with setMaterial: material, from what I've seen Obj-C style has us writing setMaterial:material. This applies to the other occurrences as well, naturally.

}
}

impl Window2 {
Expand Down Expand Up @@ -618,7 +625,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 +850,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
9 changes: 9 additions & 0 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ impl WindowBuilder {
#[inline]
pub fn with_transparency(mut self, transparent: bool) -> WindowBuilder {
self.window.transparent = transparent;
self.window.blur = !transparent;
Copy link
Member

Choose a reason for hiding this comment

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

What's the intention with the negation here?

Copy link
Author

Choose a reason for hiding this comment

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

Transparency and blur are mutually exclusive. The negation ensures that transprency and blur are always set to the opposite of each other, so that both can never be true at the same time. This avoids running code paths for transparency in backends when blur is enabled (and vice versa), which in turn avoids potential bugs and improves performance.

Writing this comment, I noticed that this obviously creates a bug. When for example with_blur(false) is called, transparency will be enabled, which is wrong. I'm going to fix this.

self
}

/// Sets whether the background of the window should be blurred.
#[inline]
pub fn with_blur(mut self, blur: bool) -> WindowBuilder {
self.window.blur = blur;
self.window.transparent = !blur;
self
}

Expand Down