Skip to content
This repository has been archived by the owner on Jul 15, 2024. It is now read-only.

Commit

Permalink
Automatically register macos CALayer delegate (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
lord authored Apr 10, 2023
1 parent 4a4d4da commit 58cd5a4
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 55 deletions.
16 changes: 1 addition & 15 deletions examples/edit_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use glazier::{
Action, Affinity, Direction, Event, HitTestPoint, InputHandler, Movement, Selection,
VerticalMovement,
},
Application, IdleToken, KeyEvent, Region, Scalable, TextFieldToken, WinHandler, WindowHandle,
Application, KeyEvent, Region, Scalable, TextFieldToken, WinHandler, WindowHandle,
};
use parley::{FontContext, Layout};
use std::any::Any;
Expand Down Expand Up @@ -114,15 +114,6 @@ impl WindowState {
}
}

#[cfg(target_os = "macos")]
fn schedule_render(&self) {
self.handle
.get_idle_handle()
.unwrap()
.schedule_idle(IdleToken::new(0));
}

#[cfg(not(target_os = "macos"))]
fn schedule_render(&self) {
self.handle.invalidate();
}
Expand Down Expand Up @@ -221,11 +212,6 @@ impl WinHandler for WindowState {
self.schedule_render();
}

fn idle(&mut self, _: IdleToken) {
self.render();
self.schedule_render();
}

fn size(&mut self, size: Size) {
self.size = size;
}
Expand Down
15 changes: 1 addition & 14 deletions examples/shello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const HEIGHT: usize = 1536;
fn main() {
let app = Application::new().unwrap();
let window = glazier::WindowBuilder::new(app.clone())
.resizable(false)
.size((WIDTH as f64 / 2., HEIGHT as f64 / 2.).into())
.handler(Box::new(WindowState::new()))
.build()
Expand Down Expand Up @@ -58,15 +57,6 @@ impl WindowState {
}
}

#[cfg(target_os = "macos")]
fn schedule_render(&self) {
self.handle
.get_idle_handle()
.unwrap()
.schedule_idle(IdleToken::new(0));
}

#[cfg(not(target_os = "macos"))]
fn schedule_render(&self) {
self.handle.invalidate();
}
Expand Down Expand Up @@ -124,10 +114,7 @@ impl WinHandler for WindowState {
self.schedule_render();
}

fn idle(&mut self, _: IdleToken) {
self.render();
self.schedule_render();
}
fn idle(&mut self, _: IdleToken) {}

fn command(&mut self, _id: u32) {}

Expand Down
15 changes: 1 addition & 14 deletions examples/wgpu-triangle/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,6 @@ impl InnerWindowState {
}
}

#[cfg(target_os = "macos")]
fn schedule_render(&self) {
self.window
.get_idle_handle()
.unwrap()
.schedule_idle(IdleToken::new(0));
}

#[cfg(not(target_os = "macos"))]
fn schedule_render(&self) {
self.window.invalidate();
}
Expand Down Expand Up @@ -190,11 +181,7 @@ impl WinHandler for WindowState {
inner.schedule_render();
}

fn idle(&mut self, _: IdleToken) {
let inner = self.inner.as_mut().unwrap();
inner.draw();
inner.schedule_render();
}
fn idle(&mut self, _: IdleToken) {}

fn size(&mut self, _: Size) {
let inner = self.inner.as_mut().unwrap();
Expand Down
91 changes: 79 additions & 12 deletions src/backend/mac/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,10 @@ struct ViewState {
idle_queue: Arc<Mutex<Vec<IdleKind>>>,
/// Tracks window focusing left clicks
focus_click: bool,
// Tracks whether we have already received the mouseExited event
/// Tracks whether we have already received the mouseExited event
mouse_left: bool,
/// Tracks whether we've installed a delegate on the sublayer
installed_layer_delegate: bool,
keyboard_state: KeyboardState,
active_text_input: Option<TextFieldToken>,
parent: Option<crate::WindowHandle>,
Expand Down Expand Up @@ -361,6 +363,8 @@ impl WindowBuilder {
.handler
.size(Size::new(frame.size.width, frame.size.height));

check_if_layer_delegate_install_needed(view, view_state);

Ok(handle)
}
}
Expand All @@ -373,7 +377,7 @@ unsafe impl Send for ViewClass {}

lazy_static! {
static ref VIEW_CLASS: ViewClass = unsafe {
let mut decl = ClassDecl::new("DruidView", class!(NSView)).expect("View class defined");
let mut decl = ClassDecl::new("GlazierView", class!(NSView)).expect("View class defined");
decl.add_ivar::<*mut c_void>("viewState");

decl.add_method(
Expand Down Expand Up @@ -593,6 +597,14 @@ lazy_static! {
let protocol = Protocol::get("NSTextInputClient").unwrap();
decl.add_protocol(protocol);

decl.add_method(
sel!(displayLayer:),
display_layer as extern fn(&mut Object, Sel, Sel),
);

let protocol = Protocol::get("CALayerDelegate").unwrap();
decl.add_protocol(protocol);

ViewClass(decl.register())
};
}
Expand Down Expand Up @@ -628,6 +640,7 @@ fn make_view(handler: Box<dyn WinHandler>) -> (id, Weak<Mutex<Vec<IdleKind>>>) {
idle_queue,
focus_click: false,
mouse_left: true,
installed_layer_delegate: false,
keyboard_state,
//text: PietText::new_with_unique_state(),
active_text_input: None,
Expand All @@ -650,7 +663,7 @@ unsafe impl Send for WindowClass {}
lazy_static! {
static ref WINDOW_CLASS: WindowClass = unsafe {
let mut decl =
ClassDecl::new("DruidWindow", class!(NSWindow)).expect("Window class defined");
ClassDecl::new("GlazierWindow", class!(NSWindow)).expect("Window class defined");
decl.add_method(
sel!(canBecomeKeyWindow),
canBecomeKeyWindow as extern "C" fn(&Object, Sel) -> BOOL,
Expand Down Expand Up @@ -729,6 +742,36 @@ fn get_mouse_buttons(mask: NSUInteger) -> MouseButtons {
buttons
}

// If the main view becomes layer-backed (often this is the case with anything that does GPU
// drawing) then we will no longer get drawRect() calls on our main view. Instead we must
// install a layer delegate that implements the CALayerDelegate protocol on the layer. Since
// a layer can be installed at any of several points within the lifecycle of a view --- ie during
// initialization, or during the first draw call --- we regularly call this function to check
// whether a delegate needs to be installed.
//
// For more info, see:
// - Layer-backed vs layer-hosting views:
// https://developer.apple.com/documentation/appkit/nsview/1483695-wantslayer?language=objc
// - StackOverflow explaining how drawRect is not called on layer-backed views:
// https://stackoverflow.com/questions/16751441/nsview-subclass-drawrect-not-called
// - CALayerDelegate protocol:
// https://developer.apple.com/documentation/quartzcore/calayerdelegate
fn check_if_layer_delegate_install_needed(view: *mut Object, view_state: &mut ViewState) {
if !view_state.installed_layer_delegate {
let layer: id = unsafe { msg_send![view, layer] };
if layer != nil {
// assume that the user is using a layer-backed view, and install our delegate on the layer.
// i expect that this code would break if the user was using a layer-hosting view, but unfortunatedly
// i'm not aware of any way to check if a view is layer-backed or layer-hosting.
let () = unsafe { msg_send![layer, setDelegate: view] };
// always invalidate the layer when first installed; otherwise the user has to think hard about
// the order of installing the layer and calling invalidate() in their initialization code
unsafe { request_anim_frame(view) };
view_state.installed_layer_delegate = true;
}
}
}

extern "C" fn mouse_down_left(this: &mut Object, _: Sel, nsevent: id) {
mouse_down(this, nsevent, MouseButton::Left);
}
Expand Down Expand Up @@ -922,11 +965,32 @@ extern "C" fn draw_rect(this: &mut Object, _: Sel, dirtyRect: NSRect) {

view_state.handler.paint(&invalid);

// sometimes layers are attached in the paint handler
check_if_layer_delegate_install_needed(this, view_state);

let superclass = msg_send![this, superclass];
let () = msg_send![super(this, superclass), drawRect: dirtyRect];
}
}

extern "C" fn display_layer(this: &mut Object, _: Sel, _: Sel) {
unsafe {
// FIXME: use the actual invalid region instead of just this bounding box.
// https://developer.apple.com/documentation/appkit/nsview/1483772-getrectsbeingdrawn?language=objc
let frame: core_graphics::display::CGRect = msg_send![this, frame];
let rect = Rect::new(0.0, 0.0, frame.size.width, frame.size.height);
let invalid = Region::from(rect);

let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);

view_state.handler.paint(&invalid);

// sometimes layers are attached in the paint handler
check_if_layer_delegate_install_needed(this, view_state);
}
}

fn run_deferred(this: &mut Object, view_state: &mut ViewState, op: DeferredOp) {
match op {
DeferredOp::SetSize(size) => set_size_deferred(this, view_state, size),
Expand Down Expand Up @@ -988,6 +1052,10 @@ extern "C" fn run_idle(this: &mut Object, _: Sel) {
extern "C" fn redraw(this: &mut Object, _: Sel) {
unsafe {
let () = msg_send![this as *const _, setNeedsDisplay: YES];
let layer: id = msg_send![this, layer];
if layer != nil {
let () = msg_send![layer, setNeedsDisplay];
}
}
}

Expand Down Expand Up @@ -1056,6 +1124,12 @@ extern "C" fn window_will_close(this: &mut Object, _: Sel, _notification: id) {
}
}

unsafe fn request_anim_frame(view: *mut Object) {
// TODO: synchronize with screen refresh rate using CVDisplayLink instead.
let () = msg_send![view, performSelectorOnMainThread: sel!(redraw)
withObject: nil waitUntilDone: NO];
}

impl WindowHandle {
pub fn show(&self) {
unsafe {
Expand Down Expand Up @@ -1088,19 +1162,12 @@ impl WindowHandle {
}

pub fn request_anim_frame(&self) {
unsafe {
// TODO: synchronize with screen refresh rate using CVDisplayLink instead.
let () = msg_send![*self.nsview.load(), performSelectorOnMainThread: sel!(redraw)
withObject: nil waitUntilDone: NO];
}
unsafe { request_anim_frame(*self.nsview.load()) }
}

// Request invalidation of the entire window contents.
pub fn invalidate(&self) {
unsafe {
// We could share impl with redraw, but we'd need to deal with nil.
let () = msg_send![*self.nsview.load(), setNeedsDisplay: YES];
}
self.request_anim_frame();
}

/// Request invalidation of one rectangle.
Expand Down

0 comments on commit 58cd5a4

Please sign in to comment.