swift - 突出显示鼠标光标下的 NSWindow

标签 swift macos cocoa appkit


因为这是相当多的代码,如果有一个示例项目可以帮助您更好地理解当前的问题,我制作了一个简单的示例项目,您可以在 GitHub 上找到它:https://github.com/dehlen/Stackoverflow


我想实现一些与 macOS 屏幕截图工具非常相似的功能。当鼠标悬停在窗口上时,窗口应该突出显示。但是,我只在突出显示用户可见的窗口部分时遇到问题。

这是该功能的屏幕截图: What it should look like

不过,我目前的实现是这样的: What it looks like

我当前的实现执行以下操作:

<强>1。获取屏幕上所有可见窗口的列表

static func all() -> [Window] {
        let options = CGWindowListOption(arrayLiteral: .excludeDesktopElements, .optionOnScreenOnly)
        let windowsListInfo = CGWindowListCopyWindowInfo(options, CGMainDisplayID()) //current window
        let infoList = windowsListInfo as! [[String: Any]]
        return infoList
            .filter { $0["kCGWindowLayer"] as! Int == 0 }
            .map { Window(
                frame: CGRect(x: ($0["kCGWindowBounds"] as! [String: Any])["X"] as! CGFloat,
                       y: ($0["kCGWindowBounds"] as! [String: Any])["Y"] as! CGFloat,
                       width: ($0["kCGWindowBounds"] as! [String: Any])["Width"] as! CGFloat,
                       height: ($0["kCGWindowBounds"] as! [String: Any])["Height"] as! CGFloat),
                applicationName: $0["kCGWindowOwnerName"] as! String)}
    }

<强>2。获取鼠标位置

private func registerMouseEvents() {
        NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) {
            self.mouseLocation = NSEvent.mouseLocation
            return $0
        }
        NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved]) { _ in
            self.mouseLocation = NSEvent.mouseLocation
        }
    }

<强>3。突出显示当前鼠标位置的窗口:

static func window(at point: CGPoint) -> Window? {
        // TODO: only if frontmost
        let list = all()
        return list.filter { $0.frame.contains(point) }.first
    }
var mouseLocation: NSPoint = NSEvent.mouseLocation {
        didSet {
            //TODO: don't highlight if its the same window
            if let window = WindowList.window(at: mouseLocation), !window.isCapture {
                highlight(window: window)
            } else {
                removeHighlight()
            }
        }
    }

 private func removeHighlight() {
        highlightWindowController?.close()
        highlightWindowController = nil
    }

    func highlight(window: Window) {
        removeHighlight()
        highlightWindowController = HighlightWindowController()
        highlightWindowController?.highlight(frame: window.frame, animate: false)
        highlightWindowController?.showWindow(nil)
    }

class HighlightWindowController: NSWindowController, NSWindowDelegate {
    // MARK: - Initializers
    init() {
        let bounds = NSRect(x: 0, y: 0, width: 100, height: 100)
        let window = NSWindow(contentRect: bounds, styleMask: .borderless, backing: .buffered, defer: true)
        window.isOpaque = false
        window.level = .screenSaver
        window.backgroundColor = NSColor.blue
        window.alphaValue = 0.2
        window.ignoresMouseEvents = true
        super.init(window: window)
        window.delegate = self
    }

    // MARK: - Public API
    func highlight(frame: CGRect, animate: Bool) {
        if animate {
            NSAnimationContext.current.duration = 0.1
        }
        let target = animate ? window?.animator() : window
        target?.setFrame(frame, display: false)
    }
}

如您所见,光标下的窗口已突出显示,但突出显示的窗口绘制在可能相交的其他窗口上方。

可能的解决方案 我可以遍历列表中的可用窗口,只找到不与其他窗口重叠的矩形,只为这部分而不是整个窗口绘制高亮矩形。

我在问自己这是否是解决此问题的更优雅、更高效的解决方案。也许我可以用绘制的 HighlightWindow 的窗口级别来解决这个问题?或者我可以利用 Apple 的任何 API 来获得所需的行为吗?

最佳答案

我弄乱了你的代码,@Ted 是正确的。 NSWindow.order(_:relativeTo)正是您所需要的。

为什么 NSWindow.level 不起作用:

使用 NSWindow.level 对您不起作用,因为普通窗口(如屏幕截图中的窗口)的窗口级别都是 0。正常。例如,如果您只是将窗口级别调整为“1”,您的突出显示 View 将显示在所有 其他窗口之上。相反,如果您将它设置为“-1”,您的突出显示 View 将出现在所有普通窗口下方和桌面上方。

使用NSWindow.order(_: relativeTo)要引入的问题

没有警告就没有好的解决方案,对吧?为了使用此方法,您必须将窗口级别设置为 0,以便它可以在其他窗口之间分层。但是,这将导致在 WindowList.window(at: mouseLocation) 方法中选择突出显示窗口。当它被选中时,您的 if 语句会删除它,因为它认为它是主窗口。这会导致闪烁。 (下面的 TLDR 中包含对此的修复)

此外,如果您尝试突出显示级别为0 的窗口,您将遇到问题。要解决此类问题,您需要找到要突出显示的窗口的窗口级别,并将突出显示窗口设置为该级别。 (我的代码没有包含对这个问题的修复)

除了上述问题之外,您还需要考虑当用户将鼠标悬停在背景窗口上并在不移动鼠标的情况下单击它时会发生什么。将会发生的是背景窗口将变为前面.. 不移动突出显示窗口。一个可能的解决方法是更新点击事件的突出显示窗口。

最后,我注意到每次用户移动鼠标时,您都会创建一个新的 HighlightWindowController + 窗口。如果您只是在鼠标移动时改变已经存在的 HighlightWindowController 的框架(而不是创建一个),系统可能会更轻一些。要隐藏它,您可以调用 NSWindowController.close() 函数,或者甚至将框架设置为 {0,0,0,0}(不确定第二个想法)。

长长的;给我们一些代码

这是我所做的。

1.更改您的窗口结构以包含窗口编号:

struct Window {
    let frame: CGRect
    let applicationName: String
    let windowNumber: Int

    init(frame: CGRect, applicationName: String, refNumber: Int) {
        self.frame = frame.flippedScreenBounds
        self.applicationName = applicationName
        self.windowNumber = refNumber
    }

    var isCapture: Bool {
        return applicationName.caseInsensitiveCompare("Capture") == .orderedSame
    }
}

2. 在您的窗口列表函数中,即 static func all() -> [Window],包括窗口编号:

refNumber: $0["kCGWindowNumber"] as! Int

3. 在您的窗口突出显示函数中,在 highlightWindowController?.showWindow(nil) 之后,相对于您要突出显示的窗口对窗口进行排序!

highlightWindowController!.window!.order(.above, relativeTo: window.windowNumber)

4. 在您的高亮 Controller 中,确保将窗口级别设置回正常:

window.level = .normal

5. 窗口现在会闪烁,为防止这种情况,请更新您的 View Controller if 语句:

    if let window = WindowList.window(at: mouseLocation) {
        if !window.isCapture {
            highlight(window: window)
        }
    } else {
        removeHighlight()
    }

祝你好运,玩得开心!

编辑:

我忘了说,我的 swift 版本是 4.2(还没有升级),所以语法可能略有不同。

关于swift - 突出显示鼠标光标下的 NSWindow,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56935106/

相关文章:

ios - 如何检测用户是否停止在 Swift 中摇动手机

ios - 如何从字符串转换为整数

objective-c - 如何在 Cocoa 框架中定义全局变量

macos - 在 macOS High Sierra 10.13.12 上使用 gdb

swift - 如何使用 Swift 在 Cocoa App 中设置 NSView 的颜色?

swift - 在 Swift OSX 中,在哪里放置代码来突出显示放置目的地?

android - RoboVM 没有看到我安装了 xCode?

objective-c - NSWorkspaceDidTerminateApplicationNotification 崩溃

ios - 崩溃 UIScrollView EXC_BAD_ACCESS

swift - 删除核心数据条目无法使用参数列表 'deleteObject' 调用 '([AnyObject]?)'