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
|
private static let handleEvent: CGEventTapCallBack = { _, type, cgEvent, _ in
|
||||||
if type == .mouseMoved {
|
if type == .mouseMoved {
|
||||||
updateDeadzoneSituation(cgEvent)
|
updateDeadzoneSituation(cgEvent)
|
||||||
|
if isAllowedToMouseHover {
|
||||||
|
App.app.thumbnailsPanel.tilesView.thumbnailOverView.updateHover()
|
||||||
|
}
|
||||||
} else if (type == .tapDisabledByUserInput || type == .tapDisabledByTimeout) && shouldBeEnabled {
|
} else if (type == .tapDisabledByUserInput || type == .tapDisabledByTimeout) && shouldBeEnabled {
|
||||||
CGEvent.tapEnable(tap: eventTap!, enable: true)
|
CGEvent.tapEnable(tap: eventTap!, enable: true)
|
||||||
}
|
}
|
||||||
@@ -64,8 +67,6 @@ class CursorEvents {
|
|||||||
let deltaY = event.locationInWindow.y - deadZoneInitialPosition.y
|
let deltaY = event.locationInWindow.y - deadZoneInitialPosition.y
|
||||||
let d = hypot(deltaX, deltaY)
|
let d = hypot(deltaX, deltaY)
|
||||||
isAllowedToMouseHover = d > 25
|
isAllowedToMouseHover = d > 25
|
||||||
if isAllowedToMouseHover {
|
|
||||||
App.app.thumbnailsPanel.tilesView.thumbnailOverView.updateHover()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,8 @@ import Carbon.HIToolbox.Events
|
|||||||
class MouseEvents {
|
class MouseEvents {
|
||||||
private static var eventTap: CFMachPort!
|
private static var eventTap: CFMachPort!
|
||||||
private static var shouldBeEnabled: Bool!
|
private static var shouldBeEnabled: Bool!
|
||||||
|
private static var mouseDownTileView: TileView?
|
||||||
|
private static var mouseDownButton: TrafficLightButton?
|
||||||
|
|
||||||
static func observe() {
|
static func observe() {
|
||||||
observe_()
|
observe_()
|
||||||
@@ -18,7 +20,7 @@ class MouseEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static func observe_() {
|
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
|
// CGEvent.tapCreate returns nil if ensureAccessibilityCheckboxIsChecked() didn't pass
|
||||||
eventTap = CGEvent.tapCreate(
|
eventTap = CGEvent.tapCreate(
|
||||||
tap: .cgSessionEventTap,
|
tap: .cgSessionEventTap,
|
||||||
@@ -42,11 +44,41 @@ class MouseEvents {
|
|||||||
if !isPointerInsideUi() {
|
if !isPointerInsideUi() {
|
||||||
return nil // focused app won't receive the event
|
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 {
|
} else if type == .leftMouseUp && cgEvent.getIntegerValueField(.mouseEventClickState) >= 1 {
|
||||||
if !isPointerInsideUi() {
|
if !isPointerInsideUi() {
|
||||||
App.app.hideUi()
|
App.app.hideUi()
|
||||||
return nil // focused app won't receive the event
|
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 {
|
} else if (type == .tapDisabledByUserInput || type == .tapDisabledByTimeout) && shouldBeEnabled {
|
||||||
CGEvent.tapEnable(tap: eventTap!, enable: true)
|
CGEvent.tapEnable(tap: eventTap!, enable: true)
|
||||||
}
|
}
|
||||||
@@ -56,4 +88,36 @@ class MouseEvents {
|
|||||||
private static func isPointerInsideUi() -> Bool {
|
private static func isPointerInsideUi() -> Bool {
|
||||||
return App.app.thumbnailsPanel.contentLayoutRect.contains(App.app.thumbnailsPanel.mouseLocationOutsideOfEventStream)
|
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
|
self.type = type
|
||||||
target = self
|
target = self
|
||||||
action = #selector(onClick)
|
action = #selector(onClick)
|
||||||
addTrackingArea(NSTrackingArea(rect: .zero, options: [.mouseEnteredAndExited, .activeInKeyWindow, .inVisibleRect], owner: self, userInfo: nil))
|
|
||||||
toolTip = tooltip
|
toolTip = tooltip
|
||||||
appearance = NSAppearance(named: .aqua)
|
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) {
|
override func draw(_ dirtyRect: NSRect) {
|
||||||
let (diskBackgroundColor, diskStrokeColor, symbolColor) = colors()
|
let (diskBackgroundColor, diskStrokeColor, symbolColor) = colors()
|
||||||
let disk = drawDisk(diskBackgroundColor, diskStrokeColor)
|
let disk = drawDisk(diskBackgroundColor, diskStrokeColor)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class TileOverView: FlippedView {
|
|||||||
var isShowingWindowControls = false
|
var isShowingWindowControls = false
|
||||||
weak var scrollView: ScrollView?
|
weak var scrollView: ScrollView?
|
||||||
var previousTarget: TileView?
|
var previousTarget: TileView?
|
||||||
|
private var previousHoveredButton: TrafficLightButton?
|
||||||
|
|
||||||
var windowControlButtons: [TrafficLightButton] { [quitButton, closeButton, minimizeButton, maximizeButton] }
|
var windowControlButtons: [TrafficLightButton] { [quitButton, closeButton, minimizeButton, maximizeButton] }
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ class TileOverView: FlippedView {
|
|||||||
func updateHover() {
|
func updateHover() {
|
||||||
guard let scrollView, !scrollView.isCurrentlyScrolling else { return }
|
guard let scrollView, !scrollView.isCurrentlyScrolling else { return }
|
||||||
let location = convert(App.app.thumbnailsPanel.mouseLocationOutsideOfEventStream, from: nil)
|
let location = convert(App.app.thumbnailsPanel.mouseLocationOutsideOfEventStream, from: nil)
|
||||||
|
updateButtonHover(location)
|
||||||
let newTarget = findTarget(location)
|
let newTarget = findTarget(location)
|
||||||
guard newTarget !== previousTarget else { return }
|
guard newTarget !== previousTarget else { return }
|
||||||
caTransaction {
|
caTransaction {
|
||||||
@@ -44,6 +46,10 @@ class TileOverView: FlippedView {
|
|||||||
hideWindowControls()
|
hideWindowControls()
|
||||||
newTarget.mouseMoved()
|
newTarget.mouseMoved()
|
||||||
showWindowControls(for: newTarget)
|
showWindowControls(for: newTarget)
|
||||||
|
let statusFrame = newTarget.convert(newTarget.statusIcons.frame, to: superview)
|
||||||
|
if statusFrame.contains(location) {
|
||||||
|
newTarget.statusIcons.ensureTooltipsInstalled()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
resetHoveredWindow()
|
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 }
|
guard let documentView = superview else { return nil }
|
||||||
for case let view as TileView in documentView.subviews {
|
for case let view as TileView in documentView.subviews {
|
||||||
let frame = view.frame
|
let frame = view.frame
|
||||||
@@ -107,6 +148,10 @@ class TileOverView: FlippedView {
|
|||||||
func hideWindowControls() {
|
func hideWindowControls() {
|
||||||
guard isShowingWindowControls else { return }
|
guard isShowingWindowControls else { return }
|
||||||
isShowingWindowControls = false
|
isShowingWindowControls = false
|
||||||
|
if let old = previousHoveredButton {
|
||||||
|
old.isMouseOver = false
|
||||||
|
previousHoveredButton = nil
|
||||||
|
}
|
||||||
for button in windowControlButtons {
|
for button in windowControlButtons {
|
||||||
button.isHidden = true
|
button.isHidden = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ class TileTitleView: NSTextField {
|
|||||||
frame.size.width = width
|
frame.size.width = width
|
||||||
}
|
}
|
||||||
|
|
||||||
override func mouseMoved(with event: NSEvent) {
|
|
||||||
// no-op here prevents tooltips from disappearing on mouseMoved
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateTruncationModeIfNeeded() {
|
func updateTruncationModeIfNeeded() {
|
||||||
let newLineBreakMode = getTruncationMode()
|
let newLineBreakMode = getTruncationMode()
|
||||||
if lineBreakMode != newLineBreakMode {
|
if lineBreakMode != newLineBreakMode {
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ class TileView: FlippedView {
|
|||||||
var indexInRow = 0
|
var indexInRow = 0
|
||||||
var numberOfViewsInRow = 0
|
var numberOfViewsInRow = 0
|
||||||
|
|
||||||
var receivedMouseDown = false
|
|
||||||
|
|
||||||
// for VoiceOver cursor
|
// for VoiceOver cursor
|
||||||
override var canBecomeKeyView: Bool { true }
|
override var canBecomeKeyView: Bool { true }
|
||||||
override var acceptsFirstResponder: Bool { true }
|
override var acceptsFirstResponder: Bool { true }
|
||||||
@@ -72,32 +70,6 @@ class TileView: FlippedView {
|
|||||||
return open != nil
|
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() {
|
func mouseMoved() {
|
||||||
updateLabelTooltipIfNeeded()
|
updateLabelTooltipIfNeeded()
|
||||||
mouseMovedCallback()
|
mouseMovedCallback()
|
||||||
@@ -582,7 +554,6 @@ class StatusIconsView: FlippedView {
|
|||||||
let measure = NSAttributedString(string: Symbols.circledNumber0.rawValue, attributes: attrs)
|
let measure = NSAttributedString(string: Symbols.circledNumber0.rawValue, attributes: attrs)
|
||||||
iconCellSize = measure.size()
|
iconCellSize = measure.size()
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
addTrackingArea(NSTrackingArea(rect: .zero, options: [.mouseEnteredAndExited, .activeAlways, .inVisibleRect], owner: self, userInfo: nil))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) { fatalError() }
|
required init?(coder: NSCoder) { fatalError() }
|
||||||
@@ -623,11 +594,7 @@ class StatusIconsView: FlippedView {
|
|||||||
needsDisplay = true
|
needsDisplay = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func mouseEntered(with event: NSEvent) {
|
func ensureTooltipsInstalled() {
|
||||||
ensureTooltipsInstalled()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func ensureTooltipsInstalled() {
|
|
||||||
guard tooltipsDirty else { return }
|
guard tooltipsDirty else { return }
|
||||||
tooltipsDirty = false
|
tooltipsDirty = false
|
||||||
removeAllToolTips()
|
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