From 88b7399e437914425933684a702fc45a9f5e9ef0 Mon Sep 17 00:00:00 2001 From: BrightShard Date: Tue, 27 Jun 2023 11:51:05 -0600 Subject: [PATCH 1/7] Swift rewrite: Mouse events & window resize event support --- src/native/macos/ffi_rust.rs | 44 --- src/native/macos/ffi_swift.rs | 119 ++++-- src/native/macos/mod.rs | 10 +- .../AppleBindings/MacOS/bscursor.swift | 26 ++ .../AppleBindings/MacOS/extensions.swift | 63 ++++ .../Sources/AppleBindings/MacOS/window.swift | 346 ++++++++++++++++++ swift/Sources/AppleBindings/bridging-header.h | 34 +- swift/Sources/AppleBindings/macos-ffi.swift | 113 +++++- swift/Sources/AppleBindings/macos.swift | 109 ------ 9 files changed, 647 insertions(+), 217 deletions(-) create mode 100644 swift/Sources/AppleBindings/MacOS/bscursor.swift create mode 100644 swift/Sources/AppleBindings/MacOS/extensions.swift create mode 100644 swift/Sources/AppleBindings/MacOS/window.swift delete mode 100644 swift/Sources/AppleBindings/macos.swift diff --git a/src/native/macos/ffi_rust.rs b/src/native/macos/ffi_rust.rs index 8ac091b..77de731 100644 --- a/src/native/macos/ffi_rust.rs +++ b/src/native/macos/ffi_rust.rs @@ -1,45 +1 @@ //! FFI functions for Swift to use - -use { - super::{ - ffi_swift::{SwiftMouseButton, SwiftMouseEvent}, - EVENT_QUEUE, - }, - crate::{ - event::{Event, EventKind}, - window::WindowHandle, - }, - std::time::Duration, -}; - -/// Lokinit's mouse event callback -#[no_mangle] -pub extern "C" fn rust_mouse_callback( - window: i32, - mouse_btn: SwiftMouseButton, - mouse_event: SwiftMouseEvent, - x: f64, - y: f64, -) { - EVENT_QUEUE.with(move |queue| { - let mouse_event = mouse_event.into_mouse_event(x, y, mouse_btn); - - queue.borrow_mut().push_back(Event { - time: Duration::ZERO, - window: WindowHandle(window as usize), - kind: EventKind::Mouse(mouse_event), - }); - }); -} - -/// Lokinit's window resize callback -#[no_mangle] -pub extern "C" fn rust_window_resize_callback(window: usize, width: u32, height: u32) { - EVENT_QUEUE.with(move |queue| { - queue.borrow_mut().push_back(Event { - time: Duration::ZERO, - window: WindowHandle(window), - kind: EventKind::Resized(width, height), - }); - }); -} diff --git a/src/native/macos/ffi_swift.rs b/src/native/macos/ffi_swift.rs index 8862298..44fb2bb 100644 --- a/src/native/macos/ffi_swift.rs +++ b/src/native/macos/ffi_swift.rs @@ -1,47 +1,96 @@ //! Bindings to the external Swift code use { - crate::event::{MouseButton, MouseEvent}, - std::ffi::c_char, + crate::{ + event::{Event, EventKind, MouseButton, MouseEvent}, + window::WindowHandle, + }, + std::{ffi::c_char, time::Duration}, }; -/// The MouseButton enum, exported for Swift #[repr(i32)] -pub enum SwiftMouseButton { - Left = 0, - Middle = 1, - Right = 2, -} -impl From for MouseButton { - fn from(value: SwiftMouseButton) -> Self { - match value { - SwiftMouseButton::Left => MouseButton::Left, - SwiftMouseButton::Right => MouseButton::Right, - SwiftMouseButton::Middle => MouseButton::Middle, - } - } -} +#[derive(Debug)] +pub enum SwiftEventType { + MouseDownLeft, + MouseDownMiddle, + MouseDownRight, + MouseDownOther, -/// The MouseEvent enum, exported for Swift -#[repr(i32)] -pub enum SwiftMouseEvent { - Pressed = 0, - Released = 1, - Moved = 2, + MouseUpLeft, + MouseUpMiddle, + MouseUpRight, + MouseUpOther, + + MouseMoved, + MouseEntered, + MouseExited, + MouseScrolled, + + WindowResized, + + KeyPressed, + KeyReleased, + KeyRepeated, + + AppQuit, +} +#[repr(C)] +pub struct SwiftEvent { + pub kind: SwiftEventType, + pub data1: i32, + pub data2: i32, + pub data3: i32, + pub window: usize, } +impl TryInto for SwiftEvent { + type Error = (); -impl SwiftMouseEvent { - /// Translates the SwiftMouseEvent enum into Lokinit's MouseEvent enum - pub fn into_mouse_event(self, x: f64, y: f64, button: SwiftMouseButton) -> MouseEvent { - let x = x as i32; - let y = y as i32; - let button = button.into(); + fn try_into(self) -> Result { + let kind = match self.kind { + SwiftEventType::MouseDownLeft => EventKind::Mouse(MouseEvent::ButtonPress( + MouseButton::Left, + self.data1, + self.data2, + )), + SwiftEventType::MouseUpLeft => EventKind::Mouse(MouseEvent::ButtonRelease( + MouseButton::Left, + self.data1, + self.data2, + )), + SwiftEventType::MouseDownRight => EventKind::Mouse(MouseEvent::ButtonPress( + MouseButton::Right, + self.data1, + self.data2, + )), + SwiftEventType::MouseUpRight => EventKind::Mouse(MouseEvent::ButtonRelease( + MouseButton::Right, + self.data1, + self.data2, + )), + SwiftEventType::MouseDownOther => EventKind::Mouse(MouseEvent::ButtonPress( + MouseButton::Other(self.data3.try_into().unwrap()), + self.data1, + self.data2, + )), + SwiftEventType::MouseUpOther => EventKind::Mouse(MouseEvent::ButtonRelease( + MouseButton::Other(self.data3.try_into().unwrap()), + self.data1, + self.data2, + )), + SwiftEventType::MouseMoved => { + EventKind::Mouse(MouseEvent::CursorMove(self.data1, self.data2)) + } + SwiftEventType::WindowResized => { + EventKind::Resized(self.data1 as u32, self.data2 as u32) + } + _ => return Err(()), + }; - match self { - Self::Pressed => MouseEvent::ButtonPress(button, x, y), - Self::Released => MouseEvent::ButtonRelease(button, x, y), - Self::Moved => MouseEvent::CursorMove(x, y), - } + Ok(Event { + time: Duration::ZERO, + window: WindowHandle(self.window), + kind, + }) } } @@ -66,5 +115,5 @@ extern "C" { /// the event loop. Instead, Lokinit calls this each time `poll_event()` /// is called, which updates the app state without getting stuck in Apple's /// run loop. - pub fn update() -> bool; + pub fn update() -> SwiftEvent; } diff --git a/src/native/macos/mod.rs b/src/native/macos/mod.rs index 7dae194..f9141c0 100644 --- a/src/native/macos/mod.rs +++ b/src/native/macos/mod.rs @@ -46,14 +46,6 @@ impl LokinitBackend for MacosBackend { } fn poll_event(&mut self) -> Option { - let mut event = None; - while event.is_none() { - // update() will return `True` if the app should terminate - if unsafe { ffi_swift::update() } { - return None; - } - event = EVENT_QUEUE.with(|queue| queue.borrow_mut().pop_front()); - } - event + unsafe { ffi_swift::update() }.try_into().ok() } } diff --git a/swift/Sources/AppleBindings/MacOS/bscursor.swift b/swift/Sources/AppleBindings/MacOS/bscursor.swift new file mode 100644 index 0000000..285e2d1 --- /dev/null +++ b/swift/Sources/AppleBindings/MacOS/bscursor.swift @@ -0,0 +1,26 @@ +#if os(macOS) + +import Foundation +import AppKit + +// Loads external cursors that aren't in NSCursor +public struct BSCursor { + static let baseCursorPath = URL(string: "file:///System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors/")! + + public static let windowResizeNorthSouth: NSCursor = fetchHICursor("resizenorthsouth") + public static let windowResizeEastWest: NSCursor = fetchHICursor("resizeeastwest") + public static let windowResizeNorthEastSouthWest: NSCursor = fetchHICursor("resizenortheastsouthwest") + public static let windowResizeNorthWestSouthEast: NSCursor = fetchHICursor("resizenorthwestsoutheast") + + // Loads cursors from HIServices.framework + // https://stackoverflow.com/a/21786835/19707043 + static func fetchHICursor(_ name: String) -> NSCursor { + let path = baseCursorPath.append(name + "/") + let image = NSImage(byReferencing: path.append("cursor.pdf")) + let info = try! NSDictionary(contentsOf: path.append("info.plist"), error: ()) + let hotspot = NSPoint(x: info.value(forKey: "hotx")! as! Double, y: info.value(forKey: "hoty")! as! Double) + return NSCursor(image: image, hotSpot: hotspot) + } +} + +#endif diff --git a/swift/Sources/AppleBindings/MacOS/extensions.swift b/swift/Sources/AppleBindings/MacOS/extensions.swift new file mode 100644 index 0000000..4ccc159 --- /dev/null +++ b/swift/Sources/AppleBindings/MacOS/extensions.swift @@ -0,0 +1,63 @@ +#if os(macOS) + +import Foundation +import AppKit + +// Used to get the currently frontmost window +extension NSApplication { + public var frontWindow: NSWindow { + self.orderedWindows[0] + } +} + +// Adds an append method, since `.appending` isn't available on older macOS versions +extension URL { + public func append(_ path: String) -> URL { + return URL(string: path, relativeTo: self)! + } +} + +// Adds convenience initializers so the events aren't so annoying to make +extension LokEvent { + init(_ event: LokEventType, _ window: UInt) { + self.init( + type: event, + data1: 0, + data2: 0, + data3: 0, + window: window + ) + } + + init(_ event: LokEventType, _ data1: Int32, _ window: UInt) { + self.init( + type: event, + data1: data1, + data2: 0, + data3: 0, + window: window + ) + } + + init(_ event: LokEventType, _ data1: Int32, _ data2: Int32, _ window: UInt) { + self.init( + type: event, + data1: data1, + data2: data2, + data3: 0, + window: window + ) + } + + init(_ event: LokEventType, _ data1: Int32, _ data2: Int32, _ data3: Int32, _ window: UInt) { + self.init( + type: event, + data1: data1, + data2: data2, + data3: data3, + window: window + ) + } +} + +#endif diff --git a/swift/Sources/AppleBindings/MacOS/window.swift b/swift/Sources/AppleBindings/MacOS/window.swift new file mode 100644 index 0000000..bc54fb3 --- /dev/null +++ b/swift/Sources/AppleBindings/MacOS/window.swift @@ -0,0 +1,346 @@ +#if os(macOS) + +import Foundation +import AppKit + +// All of the sides of a window, and its corners +public enum WindowBorderLocation { + case Left + case Right + case Top + case Bottom + case TopLeft + case TopRight + case BottomLeft + case BottomRight +} + +// A customized NSWindow, with sensible defaults and helper functions/variables +public class BSWindow: NSWindow { + // Window style masks to make it resizable and have a title bar + static let masks = NSWindow.StyleMask.init(rawValue: + NSWindow.StyleMask.titled.rawValue | + NSWindow.StyleMask.closable.rawValue | + NSWindow.StyleMask.miniaturizable.rawValue | + NSWindow.StyleMask.resizable.rawValue + ) + // The size of the box that holds the "traffic light" close/minimise/maximise buttons + static let titlebarButtonBox = NSRect( + x: 7, + y: 6, + width: 54, + height: 16 + ) + // The minimum size a window can be + static let minimumWindowSize = CGSize( + width: 50, + height: 50 + ) + + // Which part of the window is being resized right now, if it's being resized + var resizeBorder: WindowBorderLocation? = nil + // If the cursor is a non-default cursor + var nonDefaultCursor = false + + init(_ size: NSRect, _ centered: Bool, _ title: String) { + print("Making new window") + + super.init( + contentRect: size, + styleMask: Self.masks, + backing: NSWindow.BackingStoreType.buffered, + defer: false + ) + + self.title = title + + // Show the window, and make it the primary window + self.makeKeyAndOrderFront(nil) + self.makeMain() + + if centered { + self.center() + } + } + + // Mouse press down handler for windows + // Returns true if the event was handled, false if it wasn't + func leftButtonDownHandler(_ event: NSEvent) -> Bool { + if !self.isMainWindow { + print("Making self main window due to click") + self.makeKeyAndOrderFront(nil) + self.makeMain() + } + + if let border = self.checkMouseInBorder() { + self.resizeBorder = border + return true + } + + return false + } + // Mouse dragged handler for windows + // Returns an event that should be forwarded to Lokinit + func leftButtonDraggedHandler(_ event: NSEvent) -> LokEvent? { + let mousePos = self.mouseLocationOutsideOfEventStream + + // Resize the window if we're currently resizing + if let border = self.resizeBorder { + let rect: CGRect + + switch border { + case .Top: + rect = CGRect( + x: self.frame.origin.x, + y: self.frame.origin.y, + width: self.frame.width, + height: mousePos.y + ) + case .Bottom: + rect = CGRect( + x: self.frame.origin.x, + y: self.frame.origin.y + mousePos.y, + width: self.frame.width, + height: self.frame.height - mousePos.y + ) + case .Left: + rect = CGRect( + x: self.frame.origin.x + mousePos.x, + y: self.frame.origin.y, + width: self.frame.width - mousePos.x, + height: self.frame.height + ) + case .Right: + rect = CGRect( + x: self.frame.origin.x, + y: self.frame.origin.y, + width: mousePos.x, + height: self.frame.height + ) + case .TopLeft: + rect = CGRect( + x: self.frame.origin.x + mousePos.x, + y: self.frame.origin.y, + width: self.frame.width - mousePos.x, + height: mousePos.y + ) + case .TopRight: + rect = CGRect( + x: self.frame.origin.x, + y: self.frame.origin.y, + width: mousePos.x, + height: mousePos.y + ) + case .BottomLeft: + rect = CGRect( + x: self.frame.origin.x + mousePos.x, + y: self.frame.origin.y + mousePos.y, + width: self.frame.width - mousePos.x, + height: self.frame.height - mousePos.y + ) + case .BottomRight: + rect = CGRect( + x: self.frame.origin.x, + y: self.frame.origin.y + mousePos.y, + width: mousePos.x, + height: self.frame.height - mousePos.y + ) + } + + // Make sure the resized window has a valid size; it might break the window otherwise + if rect.width > Self.minimumWindowSize.width && rect.height > Self.minimumWindowSize.height { + self.setFrame(rect, display: true, animate: false) + + return LokEvent(.WindowResized, Int32(rect.width), Int32(rect.height), UInt(event.window!.windowNumber)) + } + } + + return LokEvent(.MouseMoved, Int32(mousePos.x), Int32(mousePos.y), UInt(event.window!.windowNumber)) + } + // Mouse release handler for windows + // Returns true if the event was handled, false if it wasn't + func leftButtonUpHandler(_ event: NSEvent) -> Bool { + if self.resizeBorder != nil { + self.resizeBorder = nil + + return true + } + + let mousePos = self.getMouseLocation() + for btn in self.windowButtons() { + if btn.frame.contains(mousePos) { + btn.isHighlighted = true + btn.performClick(nil) + + return true + } + } + + return false + } + // Mouse movement handler for windows + // Returns true if the event was handled, false if it wasn't + func mouseMovedHandler(_ event: NSEvent) -> Bool { + // Check if cursor is over one of the window borders + let mouseInBorder = self.checkMouseInBorder() + if let border = mouseInBorder { + switch border { + case .Top, .Bottom: + self.setCursor(BSCursor.windowResizeNorthSouth) + case .Left, .Right: + self.setCursor(BSCursor.windowResizeEastWest) + case .TopLeft, .BottomRight: + self.setCursor(BSCursor.windowResizeNorthWestSouthEast) + case .TopRight, .BottomLeft: + self.setCursor(BSCursor.windowResizeNorthEastSouthWest) + } + return true + } else if self.nonDefaultCursor { + self.setCursor(NSCursor.crosshair) + self.setCursor(NSCursor.arrow, false) + } + + // Check if cursor is over one of the titlebar buttons + let mousePos = self.getMouseLocation() + if BSWindow.titlebarButtonBox.contains(mousePos) { + for btn in self.windowButtons() { + btn.isHighlighted = true + } + + return true + } else { + for btn in self.windowButtons() { + btn.isHighlighted = false + } + } + + return false + } + + // Returns an array of all the stoplight buttons + func windowButtons() -> [NSButton] { + return [ + self.standardWindowButton(ButtonType.closeButton)!, + self.standardWindowButton(ButtonType.miniaturizeButton)!, + self.standardWindowButton(ButtonType.zoomButton)!, + ] + } + + // The mouse points apple hands us have an inverted Y-coordinate. + // This corrects the points so they're actually useable + func correctWindowPoint(_ point: NSPoint) -> NSPoint { + return NSPoint( + x: point.x, + y: self.frame.height - point.y - 1.0 + ) + } + + // Gets the mouse location in the window and corrects its location + func getMouseLocation() -> NSPoint { + return self.correctWindowPoint(self.mouseLocationOutsideOfEventStream) + } + + // Checks if the mouse is in any of the window borders + func checkMouseInBorder() -> WindowBorderLocation? { + let cornerBoxSize = 15.0 + let cornerBoxOffset = (cornerBoxSize + 1.0) / 2.0 + let sideBoxSize = 7.0 + let sideBoxOffset = (sideBoxSize + 1.0) / 2.0 + let mouseLocationWindow = self.getMouseLocation() + let mouseLocationScreen = NSEvent.mouseLocation + + let innerWindow = CGRect( + x: sideBoxOffset, + y: sideBoxOffset, + width: self.frame.width - (sideBoxOffset * 2), + height: self.frame.height - (sideBoxOffset * 2) + ) + if innerWindow.contains(mouseLocationWindow) { + return nil + } + + // print("Mouse moved to \(mouseLocation) || Window frame: \(frame)") + + // Calculate boxes for the top, bottom, left, and right edges of the window + let top = CGRect( + x: self.frame.minX, + y: self.frame.maxY - sideBoxOffset, + width: self.frame.width, + height: sideBoxSize + ) + let bottom = CGRect( + x: self.frame.minX, + y: self.frame.minY - sideBoxOffset, + width: self.frame.width, + height: sideBoxSize + ) + let left = CGRect( + x: self.frame.minX - sideBoxOffset, + y: self.frame.minY, + width: sideBoxSize, + height: self.frame.height + ) + let right = CGRect( + x: self.frame.maxX - sideBoxOffset, + y: self.frame.minY, + width: sideBoxSize, + height: self.frame.height + ) + + // Calculate boxes for the corners of the window + let topLeft = CGRect( + x: self.frame.minX - cornerBoxOffset, + y: self.frame.maxY - cornerBoxOffset, + width: cornerBoxSize, + height: cornerBoxSize + ) + let topRight = CGRect( + x: self.frame.maxX - cornerBoxOffset, + y: self.frame.maxY - cornerBoxOffset, + width: cornerBoxSize, + height: cornerBoxSize + ) + let bottomLeft = CGRect( + x: self.frame.minX - cornerBoxOffset, + y: self.frame.minY - cornerBoxOffset, + width: cornerBoxSize, + height: cornerBoxSize + ) + let bottomRight = CGRect( + x: self.frame.maxX - cornerBoxOffset, + y: self.frame.minY - cornerBoxOffset, + width: cornerBoxSize, + height: cornerBoxSize + ) + + // Return the border the mouse is on + if topLeft.contains(mouseLocationScreen) { + return .TopLeft + } else if topRight.contains(mouseLocationScreen) { + return .TopRight + } else if bottomLeft.contains(mouseLocationScreen) { + return .BottomLeft + } else if bottomRight.contains(mouseLocationScreen) { + return .BottomRight + } else if top.contains(mouseLocationScreen) { + return .Top + } else if bottom.contains(mouseLocationScreen) { + return .Bottom + } else if left.contains(mouseLocationScreen) { + return .Left + } else if right.contains(mouseLocationScreen) { + return .Right + } else { + return nil + } + } + + // Changes the cursor icon + func setCursor(_ cursor: NSCursor, _ nonDefault: Bool = true) { + self.resetCursorRects() + cursor.set() + self.nonDefaultCursor = nonDefault + } +} + +#endif diff --git a/swift/Sources/AppleBindings/bridging-header.h b/swift/Sources/AppleBindings/bridging-header.h index 7d6dac7..f822dc4 100644 --- a/swift/Sources/AppleBindings/bridging-header.h +++ b/swift/Sources/AppleBindings/bridging-header.h @@ -1,5 +1,37 @@ #include +// Swift repr of the events +typedef CF_ENUM(int, LokEventType) { + MouseDownLeft, + MouseDownMiddle, + MouseDownRight, + MouseDownOther, + + MouseUpLeft, + MouseUpMiddle, + MouseUpRight, + MouseUpOther, + + MouseMoved, + MouseEntered, + MouseExited, + MouseScrolled, + + WindowResized, + + KeyPressed, + KeyReleased, + KeyRepeated, + + AppQuit +}; +struct LokEvent { + LokEventType type; + int data1; + int data2; + int data3; + unsigned long window; +}; // Swift representation of the MouseButton and MouseEvent enums typedef CF_ENUM(int, MouseButton) { Left = 0, @@ -14,4 +46,4 @@ typedef CF_ENUM(int, MouseEvent) { // Rust FFI callbacks void rust_mouse_callback(int window, MouseButton btn, MouseEvent event, double x, double y); -void rust_window_resize_callback(unsigned long window, unsigned int width, unsigned int height); \ No newline at end of file +void rust_window_resize_callback(unsigned long window, unsigned int width, unsigned int height); diff --git a/swift/Sources/AppleBindings/macos-ffi.swift b/swift/Sources/AppleBindings/macos-ffi.swift index 2a003cf..9bc5bc7 100644 --- a/swift/Sources/AppleBindings/macos-ffi.swift +++ b/swift/Sources/AppleBindings/macos-ffi.swift @@ -1,42 +1,117 @@ #if os(macOS) +import Foundation import AppKit @_cdecl("setup") func ffiSetup() { - let nsApp = NSApplication.shared - nsApp.setActivationPolicy(NSApplication.ActivationPolicy.regular) - nsApp.activate(ignoringOtherApps: true) - nsApp.finishLaunching() + // Init NSApplication + let NSApp = NSApplication.shared + NSApp.setActivationPolicy(.regular) + NSApp.activate(ignoringOtherApps: true) + NSApp.finishLaunching() } @_cdecl("create_window") func ffiCreateWindow(x: Int, y: Int, width: Int, height: Int, centered: Bool, title: UnsafePointer) -> UInt64 { - let title = String.init(cString: title) - let size = NSRect.init(x: x, y: y, width: width, height: height) - let window = MacOSWindow.init(size, centered, title) + let title = String(cString: title) + let size = NSRect(x: x, y: y, width: width, height: height) + let window = BSWindow(size, centered, title) return UInt64(window.windowNumber) } @_cdecl("update") -func ffiUpdate() -> Bool { - if NSApp.windows.count == 0 { - return true - } +func ffiUpdate() -> LokEvent { while true { + if NSApp.windows.count == 0 { + print("Swift: quitting due to lack of windows") + return LokEvent(.AppQuit, 0) + } let event = NSApp.nextEvent( matching: NSEvent.EventTypeMask.any, - until: nil, + until: Date.distantFuture, inMode: RunLoop.Mode.default, dequeue: true - ) - if event == nil { - break + )! + switch event.type { + case .appKitDefined: + switch event.subtype { + // So far as I can tell, these events use private APIs, so we have to use sendEvent + case .applicationActivated: + NSApp.sendEvent(event) + case .applicationDeactivated: + NSApp.sendEvent(event) + case .screenChanged: + fatalError("screenChanged event not yet handled") + case .windowExposed: + fatalError("windowExposed event not yet implemented") + case .windowMoved: + event.window!.sendEvent(event) + default: + continue + } + case .systemDefined: + switch event.subtype { + case .powerOff: + fatalError("powerOff event not yet handled") + default: + continue + } + case .leftMouseDown: + let window = event.window! as! BSWindow + let handled = window.leftButtonDownHandler(event) + if !handled { + let mousePos = window.getMouseLocation() + return LokEvent(.MouseDownLeft, Int32(mousePos.x), Int32(mousePos.y), UInt(window.windowNumber)) + } + case .leftMouseDragged: + let window = event.window! as! BSWindow + let forwardEvent = window.leftButtonDraggedHandler(event) + if let event = forwardEvent { + return event + } + case .leftMouseUp: + let window = event.window! as! BSWindow + let handled = window.leftButtonUpHandler(event) + if !handled { + let mousePos = window.getMouseLocation() + return LokEvent(.MouseUpLeft, Int32(mousePos.x), Int32(mousePos.y), UInt(window.windowNumber)) + } + case .rightMouseDown: + let window = event.window! as! BSWindow + let mousePos = window.getMouseLocation() + return LokEvent(.MouseDownRight, Int32(mousePos.x), Int32(mousePos.y), UInt(window.windowNumber)) + case .rightMouseDragged: + let window = event.window! as! BSWindow + let mousePos = window.getMouseLocation() + return LokEvent(.MouseMoved, Int32(mousePos.x), Int32(mousePos.y), UInt(window.windowNumber)) + case .rightMouseUp: + let window = event.window! as! BSWindow + let mousePos = window.getMouseLocation() + return LokEvent(.MouseUpRight, Int32(mousePos.x), Int32(mousePos.y), UInt(window.windowNumber)) + case .otherMouseDown: + let window = event.window! as! BSWindow + let mousePos = window.getMouseLocation() + return LokEvent(.MouseDownOther, Int32(mousePos.x), Int32(mousePos.y), Int32(event.buttonNumber), UInt(window.windowNumber)) + case .otherMouseDragged: + let window = event.window! as! BSWindow + let mousePos = window.getMouseLocation() + return LokEvent(.MouseMoved, Int32(mousePos.x), Int32(mousePos.y), UInt(window.windowNumber)) + case .otherMouseUp: + let window = event.window! as! BSWindow + let mousePos = window.getMouseLocation() + return LokEvent(.MouseUpOther, Int32(mousePos.x), Int32(mousePos.y), Int32(event.buttonNumber), UInt(window.windowNumber)) + case .mouseMoved: + let window = NSApp.frontWindow as! BSWindow + let handled = window.mouseMovedHandler(event) + if !handled { + let mousePos = window.getMouseLocation() + return LokEvent(.MouseMoved, Int32(mousePos.x), Int32(mousePos.y), UInt(window.windowNumber)) + } + default: + continue } - - NSApp.sendEvent(event!) } - return false } -#endif \ No newline at end of file +#endif diff --git a/swift/Sources/AppleBindings/macos.swift b/swift/Sources/AppleBindings/macos.swift deleted file mode 100644 index ad2cba3..0000000 --- a/swift/Sources/AppleBindings/macos.swift +++ /dev/null @@ -1,109 +0,0 @@ -#if os(macOS) - -import AppKit; - -public class MacOSWindow: NSWindow, NSWindowDelegate { - static let masks = - NSWindow.StyleMask.titled.rawValue | - NSWindow.StyleMask.closable.rawValue | - NSWindow.StyleMask.miniaturizable.rawValue | - NSWindow.StyleMask.resizable.rawValue - - init(_ size: NSRect, _ centered: Bool, _ title: String) { - super.init( - contentRect: size, - styleMask: NSWindow.StyleMask.init(rawValue: Self.masks), - backing: NSWindow.BackingStoreType.buffered, - defer: false - ) - - self.acceptsMouseMovedEvents = true - self.title = title - self.delegate = self - - if centered { - self.center() - } - - let view = MacOSView.init(size, UInt64(self.windowNumber)) - self.contentView = view - self.makeFirstResponder(view) - - self.makeKeyAndOrderFront(nil) - } - - // Window resize event - public func windowWillResize(_ sender: NSWindow, to frameSize: NSSize) -> NSSize { - rust_window_resize_callback( - UInt(sender.windowNumber), - UInt32(frameSize.width), - UInt32(frameSize.height) - ) - - return frameSize - } -} - -public class MacOSView: NSView { - let id: UInt64 - - init(_ frame: NSRect, _ id: UInt64) { - self.id = id - super.init(frame: frame) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // Allows the user to interact with elements on the window, - // even if it isn't focused, and focus at the same time - override public func acceptsFirstMouse(for event: NSEvent?) -> Bool { - return true - } - - // The points macOS gives us for click events aren't in the View's - // local coordinate system. They aren't scaled for DPI, and their Y - // coordinate is inverted. This method adjusts points to correct that. - func translateMousePoint(_ point: NSPoint) -> (Float64, Float64) { - let scaled_point = self.convertToBacking(point) - let y = Float64(self.bounds.height) - Float64(scaled_point.y) - 1.0 - return (Float64(scaled_point.x), y) - } - - // Mouse down events - override public func mouseDown(with event: NSEvent) { - let point = self.translateMousePoint(event.locationInWindow) - rust_mouse_callback(Int32(event.windowNumber), MouseButton.Left, MouseEvent.Pressed, point.0, point.1) - } - override public func rightMouseDown(with event: NSEvent) { - let point = self.translateMousePoint(event.locationInWindow) - rust_mouse_callback(Int32(event.windowNumber), MouseButton.Right, MouseEvent.Pressed, point.0, point.1) - } - override public func otherMouseDown(with event: NSEvent) { - let point = self.translateMousePoint(event.locationInWindow) - rust_mouse_callback(Int32(event.windowNumber), MouseButton.Middle, MouseEvent.Pressed, point.0, point.1) - } - - // Mouse up events - override public func mouseUp(with event: NSEvent) { - let point = self.translateMousePoint(event.locationInWindow) - rust_mouse_callback(Int32(event.windowNumber), MouseButton.Left, MouseEvent.Released, point.0, point.1) - } - override public func rightMouseUp(with event: NSEvent) { - let point = self.translateMousePoint(event.locationInWindow) - rust_mouse_callback(Int32(event.windowNumber), MouseButton.Right, MouseEvent.Released, point.0, point.1) - } - override public func otherMouseUp(with event: NSEvent) { - let point = self.translateMousePoint(event.locationInWindow) - rust_mouse_callback(Int32(event.windowNumber), MouseButton.Middle, MouseEvent.Released, point.0, point.1) - } - - // Mouse movement events - override public func mouseMoved(with event: NSEvent) { - let point = self.translateMousePoint(event.locationInWindow) - rust_mouse_callback(Int32(event.windowNumber), MouseButton.Left, MouseEvent.Moved, point.0, point.1) - } -} - -#endif \ No newline at end of file From 258dd2c206a691b138f68d000c1b1f40371666df Mon Sep 17 00:00:00 2001 From: BrightShard Date: Tue, 27 Jun 2023 19:46:55 -0600 Subject: [PATCH 2/7] Fix cursor icon bugs --- swift/Sources/AppleBindings/MacOS/bscursor.swift | 1 + swift/Sources/AppleBindings/MacOS/window.swift | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/swift/Sources/AppleBindings/MacOS/bscursor.swift b/swift/Sources/AppleBindings/MacOS/bscursor.swift index 285e2d1..eebca53 100644 --- a/swift/Sources/AppleBindings/MacOS/bscursor.swift +++ b/swift/Sources/AppleBindings/MacOS/bscursor.swift @@ -11,6 +11,7 @@ public struct BSCursor { public static let windowResizeEastWest: NSCursor = fetchHICursor("resizeeastwest") public static let windowResizeNorthEastSouthWest: NSCursor = fetchHICursor("resizenortheastsouthwest") public static let windowResizeNorthWestSouthEast: NSCursor = fetchHICursor("resizenorthwestsoutheast") + public static let empty: NSCursor = NSCursor(image: NSImage(size: NSSize.zero), hotSpot: NSPoint.zero) // Loads cursors from HIServices.framework // https://stackoverflow.com/a/21786835/19707043 diff --git a/swift/Sources/AppleBindings/MacOS/window.swift b/swift/Sources/AppleBindings/MacOS/window.swift index bc54fb3..6c65881 100644 --- a/swift/Sources/AppleBindings/MacOS/window.swift +++ b/swift/Sources/AppleBindings/MacOS/window.swift @@ -61,15 +61,19 @@ public class BSWindow: NSWindow { if centered { self.center() } + + self.disableCursorRects() } // Mouse press down handler for windows // Returns true if the event was handled, false if it wasn't func leftButtonDownHandler(_ event: NSEvent) -> Bool { if !self.isMainWindow { - print("Making self main window due to click") + // print("Making self main window due to click") + // Yes... we need all 3 of these just to make the window the main window :dawae: self.makeKeyAndOrderFront(nil) self.makeMain() + self.becomeMain() } if let border = self.checkMouseInBorder() { @@ -196,7 +200,6 @@ public class BSWindow: NSWindow { } return true } else if self.nonDefaultCursor { - self.setCursor(NSCursor.crosshair) self.setCursor(NSCursor.arrow, false) } @@ -337,7 +340,6 @@ public class BSWindow: NSWindow { // Changes the cursor icon func setCursor(_ cursor: NSCursor, _ nonDefault: Bool = true) { - self.resetCursorRects() cursor.set() self.nonDefaultCursor = nonDefault } From 84b9fcc7551b3dadd0a5e2e055bd83bbd5bffcca Mon Sep 17 00:00:00 2001 From: BrightShard Date: Wed, 28 Jun 2023 02:33:57 -0600 Subject: [PATCH 3/7] Support window moved event, fix fast resize bug --- src/native/macos/ffi_swift.rs | 2 ++ swift/Sources/AppleBindings/MacOS/window.swift | 2 +- swift/Sources/AppleBindings/bridging-header.h | 1 + swift/Sources/AppleBindings/macos-ffi.swift | 9 ++++++++- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/native/macos/ffi_swift.rs b/src/native/macos/ffi_swift.rs index 44fb2bb..b7906a3 100644 --- a/src/native/macos/ffi_swift.rs +++ b/src/native/macos/ffi_swift.rs @@ -27,6 +27,7 @@ pub enum SwiftEventType { MouseScrolled, WindowResized, + WindowMoved, KeyPressed, KeyReleased, @@ -83,6 +84,7 @@ impl TryInto for SwiftEvent { SwiftEventType::WindowResized => { EventKind::Resized(self.data1 as u32, self.data2 as u32) } + SwiftEventType::WindowMoved => EventKind::Moved(self.data1, self.data2), _ => return Err(()), }; diff --git a/swift/Sources/AppleBindings/MacOS/window.swift b/swift/Sources/AppleBindings/MacOS/window.swift index 6c65881..9bed6c1 100644 --- a/swift/Sources/AppleBindings/MacOS/window.swift +++ b/swift/Sources/AppleBindings/MacOS/window.swift @@ -152,7 +152,7 @@ public class BSWindow: NSWindow { } // Make sure the resized window has a valid size; it might break the window otherwise - if rect.width > Self.minimumWindowSize.width && rect.height > Self.minimumWindowSize.height { + if rect.size.width > Self.minimumWindowSize.width && rect.size.height > Self.minimumWindowSize.height { self.setFrame(rect, display: true, animate: false) return LokEvent(.WindowResized, Int32(rect.width), Int32(rect.height), UInt(event.window!.windowNumber)) diff --git a/swift/Sources/AppleBindings/bridging-header.h b/swift/Sources/AppleBindings/bridging-header.h index f822dc4..37dcf53 100644 --- a/swift/Sources/AppleBindings/bridging-header.h +++ b/swift/Sources/AppleBindings/bridging-header.h @@ -18,6 +18,7 @@ typedef CF_ENUM(int, LokEventType) { MouseScrolled, WindowResized, + WindowMoved, KeyPressed, KeyReleased, diff --git a/swift/Sources/AppleBindings/macos-ffi.swift b/swift/Sources/AppleBindings/macos-ffi.swift index 9bc5bc7..7c2ef23 100644 --- a/swift/Sources/AppleBindings/macos-ffi.swift +++ b/swift/Sources/AppleBindings/macos-ffi.swift @@ -46,7 +46,14 @@ func ffiUpdate() -> LokEvent { case .windowExposed: fatalError("windowExposed event not yet implemented") case .windowMoved: - event.window!.sendEvent(event) + let window = event.window! + window.sendEvent(event) + return LokEvent( + .WindowMoved, + Int32(window.frame.origin.x), + Int32(window.frame.origin.y), + UInt(window.windowNumber) + ) default: continue } From 744b372f6b46bcf916770a9a936e35c920c77e6f Mon Sep 17 00:00:00 2001 From: BrightShard Date: Wed, 28 Jun 2023 15:51:13 -0600 Subject: [PATCH 4/7] Add window destroyed event --- examples/hello.rs | 7 +++++-- src/native/macos/ffi_swift.rs | 5 ++++- src/native/macos/mod.rs | 4 ---- swift/Sources/AppleBindings/MacOS/window.swift | 6 ++++++ swift/Sources/AppleBindings/bridging-header.h | 6 ++++-- swift/Sources/AppleBindings/macos-ffi.swift | 7 +++++++ 6 files changed, 26 insertions(+), 9 deletions(-) diff --git a/examples/hello.rs b/examples/hello.rs index 7cbe051..e4845c5 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -41,7 +41,7 @@ fn main() { // Log spam warning: it's commented for a reason // MouseEvent::CursorMove(x, y) => println!("Cursor moved to ({x}, {y})"), _ => {} - } + }, EventKind::Keyboard(event) => match event { KeyboardEvent::KeyPress(keycode) => println!("[{win:?}] Key {keycode:?} pressed"), KeyboardEvent::KeyRelease(keycode) => { @@ -49,7 +49,7 @@ fn main() { } KeyboardEvent::KeyRepeat(keycode) => println!("[{win:?}] Key {keycode:?} repeated"), KeyboardEvent::ImeCommit(commit) => println!("[{win:?}] IME commit -> {commit:?}"), - } + }, EventKind::Resized(width, height) => { println!("[{win:?}] Window resized to ({width}, {height})") } @@ -60,6 +60,9 @@ fn main() { lok::close_window(win); println!("[{win:?}] Closed upon request"); } + EventKind::Destroyed => { + println!("[{win:?}] Destroyed") + } _ => {} } } diff --git a/src/native/macos/ffi_swift.rs b/src/native/macos/ffi_swift.rs index b7906a3..ded55b1 100644 --- a/src/native/macos/ffi_swift.rs +++ b/src/native/macos/ffi_swift.rs @@ -9,7 +9,7 @@ use { }; #[repr(i32)] -#[derive(Debug)] +#[allow(dead_code)] pub enum SwiftEventType { MouseDownLeft, MouseDownMiddle, @@ -28,6 +28,8 @@ pub enum SwiftEventType { WindowResized, WindowMoved, + WindowCloseRequested, + WindowDestroyed, KeyPressed, KeyReleased, @@ -85,6 +87,7 @@ impl TryInto for SwiftEvent { EventKind::Resized(self.data1 as u32, self.data2 as u32) } SwiftEventType::WindowMoved => EventKind::Moved(self.data1, self.data2), + SwiftEventType::WindowDestroyed => EventKind::Destroyed, _ => return Err(()), }; diff --git a/src/native/macos/mod.rs b/src/native/macos/mod.rs index f9141c0..bfbd600 100644 --- a/src/native/macos/mod.rs +++ b/src/native/macos/mod.rs @@ -11,10 +11,6 @@ use { std::{cell::RefCell, collections::VecDeque, ffi::CString}, }; -thread_local! { - static EVENT_QUEUE: RefCell> = RefCell::new(VecDeque::new()); -} - pub struct MacosBackend; impl LokinitBackend for MacosBackend { diff --git a/swift/Sources/AppleBindings/MacOS/window.swift b/swift/Sources/AppleBindings/MacOS/window.swift index 9bed6c1..a1a21cb 100644 --- a/swift/Sources/AppleBindings/MacOS/window.swift +++ b/swift/Sources/AppleBindings/MacOS/window.swift @@ -343,6 +343,12 @@ public class BSWindow: NSWindow { cursor.set() self.nonDefaultCursor = nonDefault } + + // Pass the window destroyed event to Lokinit + public override func close() { + EventBuffer = LokEvent(.WindowDestroyed, UInt(self.windowNumber)) + super.close() + } } #endif diff --git a/swift/Sources/AppleBindings/bridging-header.h b/swift/Sources/AppleBindings/bridging-header.h index 37dcf53..e669dbc 100644 --- a/swift/Sources/AppleBindings/bridging-header.h +++ b/swift/Sources/AppleBindings/bridging-header.h @@ -19,6 +19,8 @@ typedef CF_ENUM(int, LokEventType) { WindowResized, WindowMoved, + WindowCloseRequested, + WindowDestroyed, KeyPressed, KeyReleased, @@ -33,6 +35,7 @@ struct LokEvent { int data3; unsigned long window; }; +typedef struct LokEvent LokEvent; // Swift representation of the MouseButton and MouseEvent enums typedef CF_ENUM(int, MouseButton) { Left = 0, @@ -46,5 +49,4 @@ typedef CF_ENUM(int, MouseEvent) { }; // Rust FFI callbacks -void rust_mouse_callback(int window, MouseButton btn, MouseEvent event, double x, double y); -void rust_window_resize_callback(unsigned long window, unsigned int width, unsigned int height); +void rust_queue_event(LokEvent event); diff --git a/swift/Sources/AppleBindings/macos-ffi.swift b/swift/Sources/AppleBindings/macos-ffi.swift index 7c2ef23..ef7f6f9 100644 --- a/swift/Sources/AppleBindings/macos-ffi.swift +++ b/swift/Sources/AppleBindings/macos-ffi.swift @@ -3,6 +3,8 @@ import Foundation import AppKit +public var EventBuffer: LokEvent? = nil + @_cdecl("setup") func ffiSetup() { // Init NSApplication @@ -118,6 +120,11 @@ func ffiUpdate() -> LokEvent { default: continue } + + if let buffer = EventBuffer { + EventBuffer = nil + return buffer + } } } From dc97a70cf3858edd7c7e4cd85294aa2662e3dfa2 Mon Sep 17 00:00:00 2001 From: BrightShard Date: Sat, 1 Jul 2023 17:47:38 -0400 Subject: [PATCH 5/7] Window focus & mouse enter/exit events --- examples/hello.rs | 2 + src/native/macos/ffi_swift.rs | 10 ++ .../Sources/AppleBindings/MacOS/window.swift | 151 ++++++++++++------ swift/Sources/AppleBindings/bridging-header.h | 2 + swift/Sources/AppleBindings/macos-ffi.swift | 14 +- 5 files changed, 119 insertions(+), 60 deletions(-) diff --git a/examples/hello.rs b/examples/hello.rs index e4845c5..90b4a36 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -63,6 +63,8 @@ fn main() { EventKind::Destroyed => { println!("[{win:?}] Destroyed") } + EventKind::FocusIn => println!("[{win:?}] Window focused"), + EventKind::FocusOut => println!("[{win:?}] Window lost focus"), _ => {} } } diff --git a/src/native/macos/ffi_swift.rs b/src/native/macos/ffi_swift.rs index ded55b1..b96e2f8 100644 --- a/src/native/macos/ffi_swift.rs +++ b/src/native/macos/ffi_swift.rs @@ -30,6 +30,8 @@ pub enum SwiftEventType { WindowMoved, WindowCloseRequested, WindowDestroyed, + WindowGainedFocus, + WindowLostFocus, KeyPressed, KeyReleased, @@ -88,6 +90,14 @@ impl TryInto for SwiftEvent { } SwiftEventType::WindowMoved => EventKind::Moved(self.data1, self.data2), SwiftEventType::WindowDestroyed => EventKind::Destroyed, + SwiftEventType::MouseEntered => { + EventKind::Mouse(MouseEvent::CursorIn(self.data1, self.data2)) + } + SwiftEventType::MouseExited => { + EventKind::Mouse(MouseEvent::CursorOut(self.data1, self.data2)) + } + SwiftEventType::WindowGainedFocus => EventKind::FocusIn, + SwiftEventType::WindowLostFocus => EventKind::FocusOut, _ => return Err(()), }; diff --git a/swift/Sources/AppleBindings/MacOS/window.swift b/swift/Sources/AppleBindings/MacOS/window.swift index a1a21cb..859958d 100644 --- a/swift/Sources/AppleBindings/MacOS/window.swift +++ b/swift/Sources/AppleBindings/MacOS/window.swift @@ -36,11 +36,18 @@ public class BSWindow: NSWindow { width: 50, height: 50 ) + // Sizes used for detecting if the cursor is over a window border + static let windowResizeCornerHitboxSize = 15.0 + static let windowResizeCornerHitboxOffset = (windowResizeCornerHitboxSize + 1.0) / 2.0 + static let windowResizeSideHitboxSize = 7.0 + static let windowResizeSideHitboxOffset = (windowResizeSideHitboxSize + 1.0) / 2.0 // Which part of the window is being resized right now, if it's being resized var resizeBorder: WindowBorderLocation? = nil // If the cursor is a non-default cursor var nonDefaultCursor = false + // If the mouse was in the window (tracks mouse entered/left events) + var mouseWasInWindow = false init(_ size: NSRect, _ centered: Bool, _ title: String) { print("Making new window") @@ -70,10 +77,7 @@ public class BSWindow: NSWindow { func leftButtonDownHandler(_ event: NSEvent) -> Bool { if !self.isMainWindow { // print("Making self main window due to click") - // Yes... we need all 3 of these just to make the window the main window :dawae: - self.makeKeyAndOrderFront(nil) - self.makeMain() - self.becomeMain() + self.focus() } if let border = self.checkMouseInBorder() { @@ -185,6 +189,27 @@ public class BSWindow: NSWindow { // Mouse movement handler for windows // Returns true if the event was handled, false if it wasn't func mouseMovedHandler(_ event: NSEvent) -> Bool { + let mousePos = self.getMouseLocation() + + // Check if the mouse entered or left the window + if self.checkMouseInWindow() && !self.mouseWasInWindow { + self.mouseWasInWindow = true + EventBuffer.append(LokEvent( + .MouseEntered, + Int32(mousePos.x), + Int32(mousePos.y), + UInt(self.windowNumber) + )) + } else if self.checkMouseOutsideWindow() && self.mouseWasInWindow { + self.mouseWasInWindow = false + EventBuffer.append(LokEvent( + .MouseExited, + Int32(mousePos.x), + Int32(mousePos.y), + UInt(self.windowNumber) + )) + } + // Check if cursor is over one of the window borders let mouseInBorder = self.checkMouseInBorder() if let border = mouseInBorder { @@ -204,7 +229,6 @@ public class BSWindow: NSWindow { } // Check if cursor is over one of the titlebar buttons - let mousePos = self.getMouseLocation() if BSWindow.titlebarButtonBox.contains(mousePos) { for btn in self.windowButtons() { btn.isHighlighted = true @@ -242,23 +266,34 @@ public class BSWindow: NSWindow { func getMouseLocation() -> NSPoint { return self.correctWindowPoint(self.mouseLocationOutsideOfEventStream) } - + + // Checks if the mouse is out of the window + func checkMouseOutsideWindow() -> Bool { + let window = CGRect( + x: -Self.windowResizeSideHitboxOffset, + y: -Self.windowResizeSideHitboxOffset, + width: self.frame.width + (Self.windowResizeSideHitboxOffset * 2), + height: self.frame.height + (Self.windowResizeSideHitboxOffset * 2) + ) + + return !window.contains(self.getMouseLocation()) + } + // Checks if the mouse is in the window + func checkMouseInWindow() -> Bool { + let innerWindow = CGRect( + x: Self.windowResizeSideHitboxOffset, + y: Self.windowResizeSideHitboxOffset, + width: self.frame.width - (Self.windowResizeSideHitboxOffset * 2), + height: self.frame.height - (Self.windowResizeSideHitboxOffset * 2) + ) + + return innerWindow.contains(self.getMouseLocation()) + } // Checks if the mouse is in any of the window borders func checkMouseInBorder() -> WindowBorderLocation? { - let cornerBoxSize = 15.0 - let cornerBoxOffset = (cornerBoxSize + 1.0) / 2.0 - let sideBoxSize = 7.0 - let sideBoxOffset = (sideBoxSize + 1.0) / 2.0 - let mouseLocationWindow = self.getMouseLocation() - let mouseLocationScreen = NSEvent.mouseLocation + let mouseLocation = NSEvent.mouseLocation - let innerWindow = CGRect( - x: sideBoxOffset, - y: sideBoxOffset, - width: self.frame.width - (sideBoxOffset * 2), - height: self.frame.height - (sideBoxOffset * 2) - ) - if innerWindow.contains(mouseLocationWindow) { + if self.checkMouseInWindow() { return nil } @@ -267,71 +302,71 @@ public class BSWindow: NSWindow { // Calculate boxes for the top, bottom, left, and right edges of the window let top = CGRect( x: self.frame.minX, - y: self.frame.maxY - sideBoxOffset, + y: self.frame.maxY - Self.windowResizeSideHitboxOffset, width: self.frame.width, - height: sideBoxSize - ) + height: Self.windowResizeSideHitboxSize + ) let bottom = CGRect( x: self.frame.minX, - y: self.frame.minY - sideBoxOffset, + y: self.frame.minY - Self.windowResizeSideHitboxOffset, width: self.frame.width, - height: sideBoxSize + height: Self.windowResizeSideHitboxSize ) let left = CGRect( - x: self.frame.minX - sideBoxOffset, + x: self.frame.minX - Self.windowResizeSideHitboxOffset, y: self.frame.minY, - width: sideBoxSize, + width: Self.windowResizeSideHitboxSize, height: self.frame.height ) let right = CGRect( - x: self.frame.maxX - sideBoxOffset, + x: self.frame.maxX - Self.windowResizeSideHitboxOffset, y: self.frame.minY, - width: sideBoxSize, + width: Self.windowResizeSideHitboxSize, height: self.frame.height ) // Calculate boxes for the corners of the window let topLeft = CGRect( - x: self.frame.minX - cornerBoxOffset, - y: self.frame.maxY - cornerBoxOffset, - width: cornerBoxSize, - height: cornerBoxSize + x: self.frame.minX - Self.windowResizeCornerHitboxOffset, + y: self.frame.maxY - Self.windowResizeCornerHitboxOffset, + width: Self.windowResizeCornerHitboxSize, + height: Self.windowResizeCornerHitboxSize ) let topRight = CGRect( - x: self.frame.maxX - cornerBoxOffset, - y: self.frame.maxY - cornerBoxOffset, - width: cornerBoxSize, - height: cornerBoxSize + x: self.frame.maxX - Self.windowResizeCornerHitboxOffset, + y: self.frame.maxY - Self.windowResizeCornerHitboxOffset, + width: Self.windowResizeCornerHitboxSize, + height: Self.windowResizeCornerHitboxSize ) let bottomLeft = CGRect( - x: self.frame.minX - cornerBoxOffset, - y: self.frame.minY - cornerBoxOffset, - width: cornerBoxSize, - height: cornerBoxSize + x: self.frame.minX - Self.windowResizeCornerHitboxOffset, + y: self.frame.minY - Self.windowResizeCornerHitboxOffset, + width: Self.windowResizeCornerHitboxSize, + height: Self.windowResizeCornerHitboxSize ) let bottomRight = CGRect( - x: self.frame.maxX - cornerBoxOffset, - y: self.frame.minY - cornerBoxOffset, - width: cornerBoxSize, - height: cornerBoxSize + x: self.frame.maxX - Self.windowResizeCornerHitboxOffset, + y: self.frame.minY - Self.windowResizeCornerHitboxOffset, + width: Self.windowResizeCornerHitboxSize, + height: Self.windowResizeCornerHitboxSize ) // Return the border the mouse is on - if topLeft.contains(mouseLocationScreen) { + if topLeft.contains(mouseLocation) { return .TopLeft - } else if topRight.contains(mouseLocationScreen) { + } else if topRight.contains(mouseLocation) { return .TopRight - } else if bottomLeft.contains(mouseLocationScreen) { + } else if bottomLeft.contains(mouseLocation) { return .BottomLeft - } else if bottomRight.contains(mouseLocationScreen) { + } else if bottomRight.contains(mouseLocation) { return .BottomRight - } else if top.contains(mouseLocationScreen) { + } else if top.contains(mouseLocation) { return .Top - } else if bottom.contains(mouseLocationScreen) { + } else if bottom.contains(mouseLocation) { return .Bottom - } else if left.contains(mouseLocationScreen) { + } else if left.contains(mouseLocation) { return .Left - } else if right.contains(mouseLocationScreen) { + } else if right.contains(mouseLocation) { return .Right } else { return nil @@ -346,9 +381,19 @@ public class BSWindow: NSWindow { // Pass the window destroyed event to Lokinit public override func close() { - EventBuffer = LokEvent(.WindowDestroyed, UInt(self.windowNumber)) + EventBuffer.append(LokEvent(.WindowDestroyed, UInt(self.windowNumber))) super.close() } + + // Make the window the main window, and send focus events to Lokinit + public func focus() { + EventBuffer.append(LokEvent(.WindowLostFocus, UInt(NSApp.frontWindow.windowNumber))) + EventBuffer.append(LokEvent(.WindowGainedFocus, UInt(self.windowNumber))) + // Yes... we need all 3 of these just to make the window the main window :dawae: + self.makeKeyAndOrderFront(nil) + self.makeMain() + self.becomeMain() + } } #endif diff --git a/swift/Sources/AppleBindings/bridging-header.h b/swift/Sources/AppleBindings/bridging-header.h index e669dbc..8a1d31e 100644 --- a/swift/Sources/AppleBindings/bridging-header.h +++ b/swift/Sources/AppleBindings/bridging-header.h @@ -21,6 +21,8 @@ typedef CF_ENUM(int, LokEventType) { WindowMoved, WindowCloseRequested, WindowDestroyed, + WindowGainedFocus, + WindowLostFocus, KeyPressed, KeyReleased, diff --git a/swift/Sources/AppleBindings/macos-ffi.swift b/swift/Sources/AppleBindings/macos-ffi.swift index ef7f6f9..e1894d5 100644 --- a/swift/Sources/AppleBindings/macos-ffi.swift +++ b/swift/Sources/AppleBindings/macos-ffi.swift @@ -3,7 +3,7 @@ import Foundation import AppKit -public var EventBuffer: LokEvent? = nil +public var EventBuffer: Array = Array() @_cdecl("setup") func ffiSetup() { @@ -26,15 +26,20 @@ func ffiCreateWindow(x: Int, y: Int, width: Int, height: Int, centered: Bool, ti func ffiUpdate() -> LokEvent { while true { if NSApp.windows.count == 0 { - print("Swift: quitting due to lack of windows") return LokEvent(.AppQuit, 0) } + + if EventBuffer.first != nil { + return EventBuffer.removeFirst() + } + let event = NSApp.nextEvent( matching: NSEvent.EventTypeMask.any, until: Date.distantFuture, inMode: RunLoop.Mode.default, dequeue: true )! + switch event.type { case .appKitDefined: switch event.subtype { @@ -120,11 +125,6 @@ func ffiUpdate() -> LokEvent { default: continue } - - if let buffer = EventBuffer { - EventBuffer = nil - return buffer - } } } From 4222a706802a4de8b7b66c8fa1ac4a316f282633 Mon Sep 17 00:00:00 2001 From: BrightShard Date: Thu, 6 Jul 2023 16:36:29 -0400 Subject: [PATCH 6/7] Middle mouse events --- src/native/macos/ffi_swift.rs | 10 ++++++++++ swift/Sources/AppleBindings/macos-ffi.swift | 17 +++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/native/macos/ffi_swift.rs b/src/native/macos/ffi_swift.rs index b96e2f8..5b73ed1 100644 --- a/src/native/macos/ffi_swift.rs +++ b/src/native/macos/ffi_swift.rs @@ -67,6 +67,16 @@ impl TryInto for SwiftEvent { self.data1, self.data2, )), + SwiftEventType::MouseUpMiddle => EventKind::Mouse(MouseEvent::ButtonRelease( + MouseButton::Middle, + self.data1, + self.data2, + )), + SwiftEventType::MouseDownMiddle => EventKind::Mouse(MouseEvent::ButtonPress( + MouseButton::Middle, + self.data1, + self.data2, + )), SwiftEventType::MouseUpRight => EventKind::Mouse(MouseEvent::ButtonRelease( MouseButton::Right, self.data1, diff --git a/swift/Sources/AppleBindings/macos-ffi.swift b/swift/Sources/AppleBindings/macos-ffi.swift index e1894d5..182a60a 100644 --- a/swift/Sources/AppleBindings/macos-ffi.swift +++ b/swift/Sources/AppleBindings/macos-ffi.swift @@ -106,15 +106,28 @@ func ffiUpdate() -> LokEvent { case .otherMouseDown: let window = event.window! as! BSWindow let mousePos = window.getMouseLocation() - return LokEvent(.MouseDownOther, Int32(mousePos.x), Int32(mousePos.y), Int32(event.buttonNumber), UInt(window.windowNumber)) + let mouseBtn = event.buttonNumber + + if mouseBtn == 2 { + return LokEvent(.MouseDownMiddle, Int32(mousePos.x), Int32(mousePos.y), UInt(window.windowNumber)) + } else { + return LokEvent(.MouseDownOther, Int32(mousePos.x), Int32(mousePos.y), Int32(mouseBtn), UInt(window.windowNumber)) + } case .otherMouseDragged: let window = event.window! as! BSWindow let mousePos = window.getMouseLocation() + return LokEvent(.MouseMoved, Int32(mousePos.x), Int32(mousePos.y), UInt(window.windowNumber)) case .otherMouseUp: let window = event.window! as! BSWindow let mousePos = window.getMouseLocation() - return LokEvent(.MouseUpOther, Int32(mousePos.x), Int32(mousePos.y), Int32(event.buttonNumber), UInt(window.windowNumber)) + let mouseBtn = event.buttonNumber + + if mouseBtn == 2 { + return LokEvent(.MouseUpMiddle, Int32(mousePos.x), Int32(mousePos.y), UInt(window.windowNumber)) + } else { + return LokEvent(.MouseUpOther, Int32(mousePos.x), Int32(mousePos.y), Int32(event.buttonNumber), UInt(window.windowNumber)) + } case .mouseMoved: let window = NSApp.frontWindow as! BSWindow let handled = window.mouseMovedHandler(event) From d8428f0ff80567d1a3a06b478a1839f03d982eaf Mon Sep 17 00:00:00 2001 From: BrightShard Date: Thu, 6 Jul 2023 21:28:24 -0400 Subject: [PATCH 7/7] macOS keyboard events --- src/native/macos/ffi_swift.rs | 21 ++- src/native/macos/keysym.rs | 137 ++++++++++++++++++++ src/native/macos/mod.rs | 1 + swift/Sources/AppleBindings/macos-ffi.swift | 18 +++ 4 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 src/native/macos/keysym.rs diff --git a/src/native/macos/ffi_swift.rs b/src/native/macos/ffi_swift.rs index 5b73ed1..9b86bc2 100644 --- a/src/native/macos/ffi_swift.rs +++ b/src/native/macos/ffi_swift.rs @@ -1,8 +1,9 @@ //! Bindings to the external Swift code use { + super::keysym, crate::{ - event::{Event, EventKind, MouseButton, MouseEvent}, + event::{Event, EventKind, KeyboardEvent, MouseButton, MouseEvent}, window::WindowHandle, }, std::{ffi::c_char, time::Duration}, @@ -108,6 +109,24 @@ impl TryInto for SwiftEvent { } SwiftEventType::WindowGainedFocus => EventKind::FocusIn, SwiftEventType::WindowLostFocus => EventKind::FocusOut, + SwiftEventType::KeyPressed => { + match keysym::to_keycode(self.data1.try_into().unwrap()) { + None => return Err(()), + Some(key) => EventKind::Keyboard(KeyboardEvent::KeyPress(key)), + } + } + SwiftEventType::KeyRepeated => { + match keysym::to_keycode(self.data1.try_into().unwrap()) { + None => return Err(()), + Some(key) => EventKind::Keyboard(KeyboardEvent::KeyRepeat(key)), + } + } + SwiftEventType::KeyReleased => { + match keysym::to_keycode(self.data1.try_into().unwrap()) { + None => return Err(()), + Some(key) => EventKind::Keyboard(KeyboardEvent::KeyRelease(key)), + } + } _ => return Err(()), }; diff --git a/src/native/macos/keysym.rs b/src/native/macos/keysym.rs new file mode 100644 index 0000000..146f2e2 --- /dev/null +++ b/src/native/macos/keysym.rs @@ -0,0 +1,137 @@ +use crate::keycode::KeyCode; + +pub fn to_keycode(keysym: u32) -> Option { + Some(match keysym { + // Big thanks to Mozilla: + // https://developer.mozilla.org/en-US/docs/web/api/ui_events/keyboard_event_code_values#code_values_on_mac + // Mozilla's always there for me when the bad fruit company isn't <3 + 0x00 => KeyCode::A, + 0x01 => KeyCode::S, + 0x02 => KeyCode::D, + 0x03 => KeyCode::F, + 0x04 => KeyCode::H, + 0x05 => KeyCode::G, + 0x06 => KeyCode::Z, + 0x07 => KeyCode::X, + 0x08 => KeyCode::C, + 0x09 => KeyCode::V, + // 0x0A => ISO section? + 0x0B => KeyCode::B, + 0x0C => KeyCode::Q, + 0x0D => KeyCode::W, + 0x0E => KeyCode::E, + 0x0F => KeyCode::R, + 0x10 => KeyCode::Y, + 0x11 => KeyCode::T, + 0x12 => KeyCode::Key1, + 0x13 => KeyCode::Key2, + 0x14 => KeyCode::Key3, + 0x15 => KeyCode::Key4, + 0x16 => KeyCode::Key6, + 0x17 => KeyCode::Key5, + 0x18 => KeyCode::Equals, + 0x19 => KeyCode::Key9, + 0x1A => KeyCode::Key7, + 0x1B => KeyCode::Minus, + 0x1C => KeyCode::Key8, + 0x1D => KeyCode::Key0, + 0x1E => KeyCode::RBracket, + 0x1F => KeyCode::O, + 0x20 => KeyCode::U, + 0x21 => KeyCode::LBracket, + 0x22 => KeyCode::I, + 0x23 => KeyCode::P, + 0x24 => KeyCode::Enter, + 0x25 => KeyCode::L, + 0x26 => KeyCode::J, + 0x27 => KeyCode::SingleQuote, + 0x28 => KeyCode::K, + 0x29 => KeyCode::Semicolon, + 0x2A => KeyCode::Backslash, + 0x2B => KeyCode::Comma, + 0x2C => KeyCode::Slash, + 0x2D => KeyCode::N, + 0x2E => KeyCode::M, + 0x2F => KeyCode::Point, + 0x30 => KeyCode::Tab, + 0x31 => KeyCode::Space, + 0x32 => KeyCode::Backtick, + 0x33 => KeyCode::Backspace, + // 0x34 => numpad enter on powerbook + 0x35 => KeyCode::Escape, + 0x36 => KeyCode::RCommand, + 0x37 => KeyCode::LCommand, + 0x38 => KeyCode::LShift, + 0x39 => KeyCode::CapsLock, + 0x3A => KeyCode::LAlt, + 0x3B => KeyCode::LCtrl, + 0x3C => KeyCode::RShift, + 0x3D => KeyCode::RAlt, + 0x3E => KeyCode::RCtrl, + // 0x3F => nil + // 0x40 => F17 + 0x41 => KeyCode::NumpadDecimal, + // 0x42 => nil + 0x43 => KeyCode::NumpadMultiply, + // 0x44 => nil + 0x45 => KeyCode::NumpadAdd, + // 0x46 => nil + 0x47 => KeyCode::NumLock, + // 0x48 => volume up + // 0x49 => volume down + // 0x4A => mute volume + 0x4B => KeyCode::NumpadDivide, + 0x4C => KeyCode::NumpadEnter, + // 0x4D => nil + 0x4E => KeyCode::NumpadSubtract, + // 0x4F => F18 + // 0x50 => F19 + 0x51 => KeyCode::NumpadEquals, + 0x52 => KeyCode::Numpad0, + 0x53 => KeyCode::Numpad1, + 0x54 => KeyCode::Numpad2, + 0x55 => KeyCode::Numpad3, + 0x56 => KeyCode::Numpad4, + 0x57 => KeyCode::Numpad5, + 0x58 => KeyCode::Numpad6, + 0x59 => KeyCode::Numpad7, + // 0x5A => F20 + 0x5B => KeyCode::Numpad8, + 0x5C => KeyCode::Numpad9, + // 0x5D => JIS yen + // 0x5E => JIS underscore + 0x5F => KeyCode::NumpadComma, + 0x60 => KeyCode::F5, + 0x61 => KeyCode::F6, + 0x62 => KeyCode::F7, + 0x63 => KeyCode::F3, + 0x64 => KeyCode::F8, + 0x65 => KeyCode::F9, + // 0x66 => JIS eisu + 0x67 => KeyCode::F11, + // 0x68 => JIS kana + // 0x69 => F13 + // 0x6A => F16 + // 0x6B => F14 + // 0x6C => nil + 0x6D => KeyCode::F10, + // 0x6E => context menu? + 0x6F => KeyCode::F12, + // 0x70 => nil + // 0x71 => F15 + // 0x72 => help or insert? + 0x73 => KeyCode::Home, + 0x74 => KeyCode::PageUp, + 0x75 => KeyCode::Delete, + 0x76 => KeyCode::F4, + 0x77 => KeyCode::End, + 0x78 => KeyCode::F2, + 0x79 => KeyCode::PageDown, + 0x7A => KeyCode::F1, + 0x7B => KeyCode::ArrowLeft, + 0x7C => KeyCode::ArrowRight, + 0x7D => KeyCode::ArrowDown, + 0x7E => KeyCode::ArrowUp, + _ => return None, + }) +} diff --git a/src/native/macos/mod.rs b/src/native/macos/mod.rs index bfbd600..ac5fbe5 100644 --- a/src/native/macos/mod.rs +++ b/src/native/macos/mod.rs @@ -2,6 +2,7 @@ use crate::lok::{CreateWindowError, LokinitBackend}; mod ffi_rust; mod ffi_swift; +mod keysym; use { crate::{ diff --git a/swift/Sources/AppleBindings/macos-ffi.swift b/swift/Sources/AppleBindings/macos-ffi.swift index 182a60a..8e2d3ce 100644 --- a/swift/Sources/AppleBindings/macos-ffi.swift +++ b/swift/Sources/AppleBindings/macos-ffi.swift @@ -4,6 +4,7 @@ import Foundation import AppKit public var EventBuffer: Array = Array() +var LastKeySym: Int32? = nil @_cdecl("setup") func ffiSetup() { @@ -135,6 +136,23 @@ func ffiUpdate() -> LokEvent { let mousePos = window.getMouseLocation() return LokEvent(.MouseMoved, Int32(mousePos.x), Int32(mousePos.y), UInt(window.windowNumber)) } + case .keyDown: + let keySym = Int32(event.keyCode) + + if keySym == LastKeySym { + return LokEvent(.KeyRepeated, keySym, UInt(event.windowNumber)) + } else { + LastKeySym = keySym + return LokEvent(.KeyPressed, keySym, UInt(event.windowNumber)) + } + case .keyUp: + let keySym = Int32(event.keyCode) + + if keySym == LastKeySym { + LastKeySym = nil + } + + return LokEvent(.KeyReleased, keySym, UInt(event.windowNumber)) default: continue }