perf: do all mouse interactions globally instead of local tracking
This commit is contained in:
1 parent
d525d350fc
commit
d89bc29425
6 files changed
+116
-57
No files matched your search
@@ -45,6 +45,9 @@ class CursorEvents {
|
||||
private static let handleEvent: CGEventTapCallBack = { _, type, cgEvent, _ in
|
||||
if type == .mouseMoved {
|
||||
updateDeadzoneSituation(cgEvent)
|
||||
if isAllowedToMouseHover {
|
||||
App.app.thumbnailsPanel.tilesView.thumbnailOverView.updateHover()
|
||||
}
|
||||
} else if (type == .tapDisabledByUserInput || type == .tapDisabledByTimeout) && shouldBeEnabled {
|
||||
CGEvent.tapEnable(tap: eventTap!, enable: true)
|
||||
}
|
||||
@@ -64,8 +67,6 @@ class CursorEvents {
|
||||
let deltaY = event.locationInWindow.y - deadZoneInitialPosition.y
|
||||
let d = hypot(deltaX, deltaY)
|
||||
isAllowedToMouseHover = d > 25
|
||||
if isAllowedToMouseHover {
|
||||
App.app.thumbnailsPanel.tilesView.thumbnailOverView.updateHover()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import Carbon.HIToolbox.Events
|
||||
class MouseEvents {
|
||||
private static var eventTap: CFMachPort!
|
||||
private static var shouldBeEnabled: Bool!
|
||||
private static var mouseDownTileView: TileView?
|
||||
private static var mouseDownButton: TrafficLightButton?
|
||||
|
||||
static func observe() {
|
||||
observe_()
|
||||
@@ -18,7 +20,7 @@ class MouseEvents {
|
||||
}
|
||||
|
||||
private static func observe_() {
|
||||
let eventMask = [CGEventType.leftMouseDown, CGEventType.leftMouseUp].reduce(CGEventMask(0), { $0 | (1 << $1.rawValue) })
|
||||
let eventMask = [CGEventType.leftMouseDown, CGEventType.leftMouseUp, CGEventType.otherMouseUp].reduce(CGEventMask(0), { $0 | (1 << $1.rawValue) })
|
||||
// CGEvent.tapCreate returns nil if ensureAccessibilityCheckboxIsChecked() didn't pass
|
||||
eventTap = CGEvent.tapCreate(
|
||||
tap: .cgSessionEventTap,
|
||||
@@ -42,11 +44,41 @@ class MouseEvents {
|
||||
if !isPointerInsideUi() {
|
||||
return nil // focused app won't receive the event
|
||||
}
|
||||
if let button = findButtonUnderPointer() {
|
||||
mouseDownButton = button
|
||||
button.isHighlighted = true
|
||||
button.setNeedsDisplay()
|
||||
return nil // swallow - we handle button clicks centrally
|
||||
}
|
||||
mouseDownTileView = findTileViewUnderPointer()
|
||||
return nil // swallow - we handle tile clicks centrally
|
||||
} else if type == .leftMouseUp && cgEvent.getIntegerValueField(.mouseEventClickState) >= 1 {
|
||||
if !isPointerInsideUi() {
|
||||
App.app.hideUi()
|
||||
return nil // focused app won't receive the event
|
||||
}
|
||||
if let button = mouseDownButton {
|
||||
mouseDownButton = nil
|
||||
button.isHighlighted = false
|
||||
button.setNeedsDisplay()
|
||||
if findButtonUnderPointer() === button {
|
||||
button.onClick()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if let target = mouseDownTileView {
|
||||
mouseDownTileView = nil
|
||||
if isPointerOverTileView(target) {
|
||||
target.mouseUpCallback()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
} else if type == .otherMouseUp {
|
||||
guard isPointerInsideUi() else { return Unmanaged.passUnretained(cgEvent) }
|
||||
if cgEvent.getIntegerValueField(.mouseEventButtonNumber) == 2 {
|
||||
handleMiddleClick()
|
||||
return nil
|
||||
}
|
||||
} else if (type == .tapDisabledByUserInput || type == .tapDisabledByTimeout) && shouldBeEnabled {
|
||||
CGEvent.tapEnable(tap: eventTap!, enable: true)
|
||||
}
|
||||
@@ -56,4 +88,36 @@ class MouseEvents {
|
||||
private static func isPointerInsideUi() -> Bool {
|
||||
return App.app.thumbnailsPanel.contentLayoutRect.contains(App.app.thumbnailsPanel.mouseLocationOutsideOfEventStream)
|
||||
}
|
||||
|
||||
private static func findButtonUnderPointer() -> TrafficLightButton? {
|
||||
let panel = App.app.thumbnailsPanel!
|
||||
let overlay = panel.tilesView.thumbnailOverView
|
||||
let windowLocation = panel.mouseLocationOutsideOfEventStream
|
||||
let overlayLocation = overlay.convert(windowLocation, from: nil)
|
||||
return overlay.findButton(overlayLocation)
|
||||
}
|
||||
|
||||
private static func findTileViewUnderPointer() -> TileView? {
|
||||
let panel = App.app.thumbnailsPanel!
|
||||
let overlay = panel.tilesView.thumbnailOverView
|
||||
let windowLocation = panel.mouseLocationOutsideOfEventStream
|
||||
let overlayLocation = overlay.convert(windowLocation, from: nil)
|
||||
return overlay.findTarget(overlayLocation)
|
||||
}
|
||||
|
||||
private static func isPointerOverTileView(_ tileView: TileView) -> Bool {
|
||||
let panel = App.app.thumbnailsPanel!
|
||||
let windowLocation = panel.mouseLocationOutsideOfEventStream
|
||||
let localPoint = tileView.convert(windowLocation, from: nil)
|
||||
return tileView.bounds.contains(localPoint)
|
||||
}
|
||||
|
||||
private static func handleMiddleClick() {
|
||||
guard let target = findTileViewUnderPointer(), let window = target.window_ else { return }
|
||||
if window.isWindowlessApp {
|
||||
window.application.quit()
|
||||
} else {
|
||||
window.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ class TrafficLightButton: NSButton {
|
||||
self.type = type
|
||||
target = self
|
||||
action = #selector(onClick)
|
||||
addTrackingArea(NSTrackingArea(rect: .zero, options: [.mouseEnteredAndExited, .activeInKeyWindow, .inVisibleRect], owner: self, userInfo: nil))
|
||||
toolTip = tooltip
|
||||
appearance = NSAppearance(named: .aqua)
|
||||
}
|
||||
@@ -34,16 +33,6 @@ class TrafficLightButton: NSButton {
|
||||
}
|
||||
}
|
||||
|
||||
override func mouseEntered(with event: NSEvent) {
|
||||
isMouseOver = true
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
override func mouseExited(with event: NSEvent) {
|
||||
isMouseOver = false
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
let (diskBackgroundColor, diskStrokeColor, symbolColor) = colors()
|
||||
let disk = drawDisk(diskBackgroundColor, diskStrokeColor)
|
||||
|
||||
@@ -8,6 +8,7 @@ class TileOverView: FlippedView {
|
||||
var isShowingWindowControls = false
|
||||
weak var scrollView: ScrollView?
|
||||
var previousTarget: TileView?
|
||||
private var previousHoveredButton: TrafficLightButton?
|
||||
|
||||
var windowControlButtons: [TrafficLightButton] { [quitButton, closeButton, minimizeButton, maximizeButton] }
|
||||
|
||||
@@ -37,6 +38,7 @@ class TileOverView: FlippedView {
|
||||
func updateHover() {
|
||||
guard let scrollView, !scrollView.isCurrentlyScrolling else { return }
|
||||
let location = convert(App.app.thumbnailsPanel.mouseLocationOutsideOfEventStream, from: nil)
|
||||
updateButtonHover(location)
|
||||
let newTarget = findTarget(location)
|
||||
guard newTarget !== previousTarget else { return }
|
||||
caTransaction {
|
||||
@@ -44,6 +46,10 @@ class TileOverView: FlippedView {
|
||||
hideWindowControls()
|
||||
newTarget.mouseMoved()
|
||||
showWindowControls(for: newTarget)
|
||||
let statusFrame = newTarget.convert(newTarget.statusIcons.frame, to: superview)
|
||||
if statusFrame.contains(location) {
|
||||
newTarget.statusIcons.ensureTooltipsInstalled()
|
||||
}
|
||||
} else {
|
||||
resetHoveredWindow()
|
||||
}
|
||||
@@ -51,7 +57,42 @@ class TileOverView: FlippedView {
|
||||
}
|
||||
}
|
||||
|
||||
private func findTarget(_ location: NSPoint) -> TileView? {
|
||||
/// Find the TrafficLightButton at the given point (in TileOverView's coordinate space)
|
||||
func findButton(_ location: NSPoint) -> TrafficLightButton? {
|
||||
guard isShowingWindowControls else { return nil }
|
||||
for button in windowControlButtons where !button.isHidden {
|
||||
let buttonPoint = button.convert(location, from: self)
|
||||
if button.bounds.contains(buttonPoint) {
|
||||
return button
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func updateButtonHover(_ location: NSPoint) {
|
||||
var newHoveredButton: TrafficLightButton?
|
||||
if isShowingWindowControls {
|
||||
for button in windowControlButtons where !button.isHidden {
|
||||
let buttonPoint = button.convert(location, from: self)
|
||||
if button.bounds.contains(buttonPoint) {
|
||||
newHoveredButton = button
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
guard newHoveredButton !== previousHoveredButton else { return }
|
||||
if let old = previousHoveredButton {
|
||||
old.isMouseOver = false
|
||||
old.setNeedsDisplay()
|
||||
}
|
||||
if let new = newHoveredButton {
|
||||
new.isMouseOver = true
|
||||
new.setNeedsDisplay()
|
||||
}
|
||||
previousHoveredButton = newHoveredButton
|
||||
}
|
||||
|
||||
func findTarget(_ location: NSPoint) -> TileView? {
|
||||
guard let documentView = superview else { return nil }
|
||||
for case let view as TileView in documentView.subviews {
|
||||
let frame = view.frame
|
||||
@@ -107,6 +148,10 @@ class TileOverView: FlippedView {
|
||||
func hideWindowControls() {
|
||||
guard isShowingWindowControls else { return }
|
||||
isShowingWindowControls = false
|
||||
if let old = previousHoveredButton {
|
||||
old.isMouseOver = false
|
||||
previousHoveredButton = nil
|
||||
}
|
||||
for button in windowControlButtons {
|
||||
button.isHidden = true
|
||||
}
|
||||
|
||||
@@ -31,10 +31,6 @@ class TileTitleView: NSTextField {
|
||||
frame.size.width = width
|
||||
}
|
||||
|
||||
override func mouseMoved(with event: NSEvent) {
|
||||
// no-op here prevents tooltips from disappearing on mouseMoved
|
||||
}
|
||||
|
||||
func updateTruncationModeIfNeeded() {
|
||||
let newLineBreakMode = getTruncationMode()
|
||||
if lineBreakMode != newLineBreakMode {
|
||||
|
||||
@@ -25,8 +25,6 @@ class TileView: FlippedView {
|
||||
var indexInRow = 0
|
||||
var numberOfViewsInRow = 0
|
||||
|
||||
var receivedMouseDown = false
|
||||
|
||||
// for VoiceOver cursor
|
||||
override var canBecomeKeyView: Bool { true }
|
||||
override var acceptsFirstResponder: Bool { true }
|
||||
@@ -72,32 +70,6 @@ class TileView: FlippedView {
|
||||
return open != nil
|
||||
}
|
||||
|
||||
override func mouseDown(with event: NSEvent) {
|
||||
receivedMouseDown = true
|
||||
}
|
||||
|
||||
override func mouseUp(with event: NSEvent) {
|
||||
if receivedMouseDown {
|
||||
if bounds.contains(convert(event.locationInWindow, from: nil)) {
|
||||
mouseUpCallback()
|
||||
}
|
||||
receivedMouseDown = false
|
||||
}
|
||||
}
|
||||
|
||||
override func otherMouseUp(with event: NSEvent) {
|
||||
// middle-click
|
||||
if event.buttonNumber == 2 {
|
||||
if let window_ {
|
||||
if window_.isWindowlessApp {
|
||||
window_.application.quit()
|
||||
} else {
|
||||
window_.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mouseMoved() {
|
||||
updateLabelTooltipIfNeeded()
|
||||
mouseMovedCallback()
|
||||
@@ -582,7 +554,6 @@ class StatusIconsView: FlippedView {
|
||||
let measure = NSAttributedString(string: Symbols.circledNumber0.rawValue, attributes: attrs)
|
||||
iconCellSize = measure.size()
|
||||
super.init(frame: frame)
|
||||
addTrackingArea(NSTrackingArea(rect: .zero, options: [.mouseEnteredAndExited, .activeAlways, .inVisibleRect], owner: self, userInfo: nil))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError() }
|
||||
@@ -623,11 +594,7 @@ class StatusIconsView: FlippedView {
|
||||
needsDisplay = true
|
||||
}
|
||||
|
||||
override func mouseEntered(with event: NSEvent) {
|
||||
ensureTooltipsInstalled()
|
||||
}
|
||||
|
||||
private func ensureTooltipsInstalled() {
|
||||
func ensureTooltipsInstalled() {
|
||||
guard tooltipsDirty else { return }
|
||||
tooltipsDirty = false
|
||||
removeAllToolTips()
|
||||
@@ -661,7 +628,4 @@ class StatusIconsView: FlippedView {
|
||||
}
|
||||
}
|
||||
|
||||
override func mouseMoved(with event: NSEvent) {
|
||||
// no-op prevents tooltips from disappearing on mouseMoved
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user