use std::cell::RefCell;
use denise::{ElementState, InputEvent, Modifiers, Point, PointerButton, Rect, Size, Surface};
use objc2::rc::Retained;
use objc2::{AnyThread, DefinedClass, MainThreadOnly, define_class, msg_send};
use objc2_app_kit::{
NSEvent, NSEventModifierFlags, NSGraphicsContext, NSTrackingArea, NSTrackingAreaOptions, NSView,
};
use objc2_foundation::{MainThreadMarker, NSPoint, NSRect, NSSize};
use crate::Error;
use crate::keymap::key_code;
use crate::surface::ViewSurface;
const LINE_HEIGHT_PX: f32 = 16.0;
pub trait ViewDelegate {
fn update(&mut self, surface: &mut ViewSurface, events: &[InputEvent], damage: &mut Vec<Rect>);
fn next_wake_ms(&self) -> Option<u64> {
None
}
}
pub struct ViewState {
pub surface: ViewSurface,
delegate: Box<dyn ViewDelegate>,
events: Vec<InputEvent>,
damage: Vec<Rect>,
modifiers: Modifiers,
tracking: Option<Retained<NSTrackingArea>>,
}
impl ViewState {
fn push(&mut self, event: InputEvent) {
self.events.push(event);
}
}
define_class!(
#[unsafe(super(NSView))]
#[thread_kind = MainThreadOnly]
#[name = "DeniseView"]
#[ivars = RefCell<ViewState>]
pub struct DeniseView;
impl DeniseView {
#[unsafe(method(isFlipped))]
fn is_flipped(&self) -> bool {
true
}
#[unsafe(method(acceptsFirstResponder))]
fn accepts_first_responder(&self) -> bool {
true
}
#[unsafe(method(acceptsFirstMouse:))]
fn accepts_first_mouse(&self, _event: Option<&NSEvent>) -> bool {
true
}
#[unsafe(method(drawRect:))]
fn draw_rect(&self, _dirty: NSRect) {
let Some(context) = NSGraphicsContext::currentContext() else {
return;
};
let state = self.ivars().borrow();
let bounds = self.bounds();
unsafe { state.surface.draw_into(&context.CGContext(), bounds) };
}
#[unsafe(method(updateTrackingAreas))]
fn update_tracking_areas(&self) {
let _: () = unsafe { msg_send![super(self), updateTrackingAreas] };
self.install_tracking_area();
}
#[unsafe(method(deniseTick:))]
fn denise_tick(&self, _timer: *mut objc2::runtime::AnyObject) {
self.update();
}
#[unsafe(method(viewDidChangeBackingProperties))]
fn backing_properties_changed(&self) {
self.sync_surface_size();
}
#[unsafe(method(mouseMoved:))]
fn mouse_moved(&self, event: &NSEvent) {
self.pointer_moved(event);
}
#[unsafe(method(mouseDragged:))]
fn mouse_dragged(&self, event: &NSEvent) {
self.pointer_moved(event);
}
#[unsafe(method(rightMouseDragged:))]
fn right_mouse_dragged(&self, event: &NSEvent) {
self.pointer_moved(event);
}
#[unsafe(method(otherMouseDragged:))]
fn other_mouse_dragged(&self, event: &NSEvent) {
self.pointer_moved(event);
}
#[unsafe(method(mouseExited:))]
fn mouse_exited(&self, _event: &NSEvent) {
self.ivars().borrow_mut().push(InputEvent::PointerLeft);
self.update();
}
#[unsafe(method(mouseDown:))]
fn mouse_down(&self, event: &NSEvent) {
self.pointer_button(event, PointerButton::Left, ElementState::Down);
}
#[unsafe(method(mouseUp:))]
fn mouse_up(&self, event: &NSEvent) {
self.pointer_button(event, PointerButton::Left, ElementState::Up);
}
#[unsafe(method(rightMouseDown:))]
fn right_mouse_down(&self, event: &NSEvent) {
self.pointer_button(event, PointerButton::Right, ElementState::Down);
}
#[unsafe(method(rightMouseUp:))]
fn right_mouse_up(&self, event: &NSEvent) {
self.pointer_button(event, PointerButton::Right, ElementState::Up);
}
#[unsafe(method(otherMouseDown:))]
fn other_mouse_down(&self, event: &NSEvent) {
let button = other_button(event);
self.pointer_button(event, button, ElementState::Down);
}
#[unsafe(method(otherMouseUp:))]
fn other_mouse_up(&self, event: &NSEvent) {
let button = other_button(event);
self.pointer_button(event, button, ElementState::Up);
}
#[unsafe(method(scrollWheel:))]
fn scroll_wheel(&self, event: &NSEvent) {
let position = self.event_position(event);
let precise = event.hasPreciseScrollingDeltas();
let scale = if precise { 1.0 } else { LINE_HEIGHT_PX };
let backing = self.ivars().borrow().surface.scale_factor();
let delta_x = -(event.scrollingDeltaX() as f32) * scale * backing;
let delta_y = -(event.scrollingDeltaY() as f32) * scale * backing;
self.ivars().borrow_mut().push(InputEvent::PointerScroll {
delta_x,
delta_y,
position,
});
self.update();
}
#[unsafe(method(keyDown:))]
fn key_down(&self, event: &NSEvent) {
self.key(event, ElementState::Down);
}
#[unsafe(method(keyUp:))]
fn key_up(&self, event: &NSEvent) {
self.key(event, ElementState::Up);
}
#[unsafe(method(flagsChanged:))]
fn flags_changed(&self, event: &NSEvent) {
let modifiers = modifiers_of(event);
let code = key_code(event.keyCode());
let was = self.ivars().borrow().modifiers;
let state = if bit_count(modifiers) > bit_count(was) {
ElementState::Down
} else {
ElementState::Up
};
let mut state_ref = self.ivars().borrow_mut();
state_ref.modifiers = modifiers;
state_ref.push(InputEvent::Key {
code,
state,
repeat: false,
modifiers,
});
drop(state_ref);
self.update();
}
}
);
impl DeniseView {
pub fn new(
mtm: MainThreadMarker,
frame: NSRect,
scale_factor: f32,
delegate: Box<dyn ViewDelegate>,
) -> Result<Retained<Self>, Error> {
let size = physical_size(frame.size, scale_factor);
let surface = ViewSurface::new(size, scale_factor)?;
let this = Self::alloc(mtm).set_ivars(RefCell::new(ViewState {
surface,
delegate,
events: Vec::new(),
damage: Vec::new(),
modifiers: Modifiers::NONE,
tracking: None,
}));
let this: Retained<Self> = unsafe { msg_send![super(this), initWithFrame: frame] };
this.install_tracking_area();
Ok(this)
}
pub fn state(&self) -> &RefCell<ViewState> {
self.ivars()
}
pub fn update(&self) {
let mut borrow = self.ivars().borrow_mut();
let state = &mut *borrow;
state.damage.clear();
let events = core::mem::take(&mut state.events);
state
.delegate
.update(&mut state.surface, &events, &mut state.damage);
state.events = events;
state.events.clear();
let rects: Vec<NSRect> = state
.damage
.iter()
.map(|rect| state.surface.damage_to_points(*rect))
.map(|cg| {
NSRect::new(
NSPoint::new(cg.origin.x, cg.origin.y),
NSSize::new(cg.size.width, cg.size.height),
)
})
.collect();
drop(borrow);
for rect in rects {
self.setNeedsDisplayInRect(rect);
}
}
pub fn next_wake_ms(&self) -> Option<u64> {
self.ivars().borrow().delegate.next_wake_ms()
}
pub fn sync_surface_size(&self) -> bool {
let bounds = self.bounds();
let scale = self.backing_scale();
let size = physical_size(bounds.size, scale);
if size.is_empty() {
return false;
}
let changed = self
.ivars()
.borrow_mut()
.surface
.resize(size, scale)
.unwrap_or(false);
if changed {
self.setNeedsDisplay(true);
}
changed
}
fn backing_scale(&self) -> f32 {
let unit = NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(1.0, 1.0));
let backing = self.convertRectToBacking(unit);
if backing.size.width > 0.0 {
backing.size.width as f32
} else {
1.0
}
}
fn install_tracking_area(&self) {
if let Some(old) = self.ivars().borrow_mut().tracking.take() {
self.removeTrackingArea(&old);
}
let options = NSTrackingAreaOptions::MouseEnteredAndExited
| NSTrackingAreaOptions::MouseMoved
| NSTrackingAreaOptions::ActiveInKeyWindow
| NSTrackingAreaOptions::InVisibleRect;
let area = unsafe {
NSTrackingArea::initWithRect_options_owner_userInfo(
NSTrackingArea::alloc(),
self.bounds(),
options,
Some(self),
None,
)
};
self.addTrackingArea(&area);
self.ivars().borrow_mut().tracking = Some(area);
}
fn event_position(&self, event: &NSEvent) -> Point {
let window_point = event.locationInWindow();
let local = self.convertPoint_fromView(window_point, None);
let scale = self.backing_scale();
Point::new(
(local.x as f32 * scale).round() as i32,
(local.y as f32 * scale).round() as i32,
)
}
fn pointer_moved(&self, event: &NSEvent) {
let position = self.event_position(event);
self.ivars()
.borrow_mut()
.push(InputEvent::PointerMoved { position });
self.update();
}
fn pointer_button(&self, event: &NSEvent, button: PointerButton, state: ElementState) {
let position = self.event_position(event);
let modifiers = modifiers_of(event);
let mut ivars = self.ivars().borrow_mut();
ivars.modifiers = modifiers;
ivars.push(InputEvent::PointerButton {
button,
state,
position,
modifiers,
});
drop(ivars);
self.update();
}
fn key(&self, event: &NSEvent, state: ElementState) {
let code = key_code(event.keyCode());
let modifiers = modifiers_of(event);
let repeat = state == ElementState::Down && event.isARepeat();
let mut ivars = self.ivars().borrow_mut();
ivars.modifiers = modifiers;
ivars.push(InputEvent::Key {
code,
state,
repeat,
modifiers,
});
if state == ElementState::Down
&& !modifiers.contains(Modifiers::CTRL)
&& !modifiers.contains(Modifiers::SUPER)
&& let Some(characters) = event.characters()
{
for ch in characters.to_string().chars().filter(|c| !c.is_control()) {
ivars.push(InputEvent::Text { ch });
}
}
drop(ivars);
self.update();
}
}
fn physical_size(size: NSSize, scale_factor: f32) -> Size {
let scale = scale_factor.max(0.01);
Size::new(
(size.width as f32 * scale).round().max(0.0) as u32,
(size.height as f32 * scale).round().max(0.0) as u32,
)
}
fn modifiers_of(event: &NSEvent) -> Modifiers {
let flags = event.modifierFlags();
let mut out = Modifiers::NONE;
for (flag, modifier) in [
(NSEventModifierFlags::Shift, Modifiers::SHIFT),
(NSEventModifierFlags::Control, Modifiers::CTRL),
(NSEventModifierFlags::Option, Modifiers::ALT),
(NSEventModifierFlags::Command, Modifiers::SUPER),
] {
if flags.contains(flag) {
out |= modifier;
}
}
out
}
fn bit_count(modifiers: Modifiers) -> u32 {
[
Modifiers::SHIFT,
Modifiers::CTRL,
Modifiers::ALT,
Modifiers::SUPER,
]
.into_iter()
.filter(|m| modifiers.contains(*m))
.count() as u32
}
fn other_button(event: &NSEvent) -> PointerButton {
match event.buttonNumber() {
2 => PointerButton::Middle,
other => PointerButton::Other(other.max(0) as u16),
}
}