mirror of https://github.com/mpv-player/mpv
218 lines
7.1 KiB
Swift
218 lines
7.1 KiB
Swift
/*
|
|
* This file is part of mpv.
|
|
*
|
|
* mpv is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* mpv is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import Cocoa
|
|
|
|
class View: NSView, CALayerDelegate {
|
|
unowned var common: Common
|
|
var input: InputHelper? { get { return common.input } }
|
|
|
|
var tracker: NSTrackingArea?
|
|
var hasMouseDown: Bool = false
|
|
|
|
override var isFlipped: Bool { return true }
|
|
override var acceptsFirstResponder: Bool { return true }
|
|
|
|
|
|
init(frame: NSRect, common com: Common) {
|
|
common = com
|
|
super.init(frame: frame)
|
|
autoresizingMask = [.width, .height]
|
|
wantsBestResolutionOpenGLSurface = true
|
|
registerForDraggedTypes([ .fileURL, .URL, .string ])
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func updateTrackingAreas() {
|
|
if let tracker = self.tracker {
|
|
removeTrackingArea(tracker)
|
|
}
|
|
|
|
tracker = NSTrackingArea(rect: bounds,
|
|
options: [.activeAlways, .mouseEnteredAndExited, .mouseMoved, .enabledDuringMouseDrag],
|
|
owner: self, userInfo: nil)
|
|
// here tracker is guaranteed to be none-nil
|
|
addTrackingArea(tracker!)
|
|
|
|
if containsMouseLocation() {
|
|
input?.put(key: SWIFT_KEY_MOUSE_LEAVE)
|
|
}
|
|
}
|
|
|
|
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
|
|
guard let types = sender.draggingPasteboard.types else { return [] }
|
|
if types.contains(.fileURL) || types.contains(.URL) || types.contains(.string) {
|
|
return .copy
|
|
}
|
|
return []
|
|
}
|
|
|
|
func isURL(_ str: String) -> Bool {
|
|
// force unwrapping is fine here, regex is guaranteed to be valid
|
|
let regex = try! NSRegularExpression(pattern: "^(https?|ftp)://[^\\s/$.?#].[^\\s]*$",
|
|
options: .caseInsensitive)
|
|
let isURL = regex.numberOfMatches(in: str,
|
|
options: [],
|
|
range: NSRange(location: 0, length: str.count))
|
|
return isURL > 0
|
|
}
|
|
|
|
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
|
|
let pb = sender.draggingPasteboard
|
|
guard let types = pb.types else { return false }
|
|
var files: [String] = []
|
|
|
|
if types.contains(.fileURL) || types.contains(.URL) {
|
|
guard let urls = pb.readObjects(forClasses: [NSURL.self]) as? [URL] else { return false }
|
|
files = urls.map { $0.absoluteString }
|
|
} else if types.contains(.string) {
|
|
guard let str = pb.string(forType: .string) else { return false }
|
|
files = str.components(separatedBy: "\n").compactMap {
|
|
let url = $0.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let path = (url as NSString).expandingTildeInPath
|
|
if isURL(url) { return url }
|
|
if path.starts(with: "/") { return path }
|
|
return nil
|
|
}
|
|
}
|
|
if files.isEmpty { return false }
|
|
input?.open(files: files)
|
|
return true
|
|
}
|
|
|
|
override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
|
|
return true
|
|
}
|
|
|
|
override func becomeFirstResponder() -> Bool {
|
|
return true
|
|
}
|
|
|
|
override func resignFirstResponder() -> Bool {
|
|
return true
|
|
}
|
|
|
|
override func mouseEntered(with event: NSEvent) {
|
|
if input?.mouseEnabled() ?? true {
|
|
input?.put(key: SWIFT_KEY_MOUSE_ENTER)
|
|
}
|
|
common.updateCursorVisibility()
|
|
}
|
|
|
|
override func mouseExited(with event: NSEvent) {
|
|
if input?.mouseEnabled() ?? true {
|
|
input?.put(key: SWIFT_KEY_MOUSE_LEAVE)
|
|
}
|
|
common.titleBar?.hide()
|
|
common.setCursorVisibility(true)
|
|
}
|
|
|
|
override func mouseMoved(with event: NSEvent) {
|
|
signalMouseMovement(event)
|
|
common.titleBar?.show()
|
|
}
|
|
|
|
override func mouseDragged(with event: NSEvent) {
|
|
signalMouseMovement(event)
|
|
}
|
|
|
|
override func mouseDown(with event: NSEvent) {
|
|
hasMouseDown = event.clickCount <= 1
|
|
input?.processMouse(event: event)
|
|
}
|
|
|
|
override func mouseUp(with event: NSEvent) {
|
|
hasMouseDown = false
|
|
common.window?.isMoving = false
|
|
input?.processMouse(event: event)
|
|
}
|
|
|
|
override func rightMouseDown(with event: NSEvent) {
|
|
hasMouseDown = event.clickCount <= 1
|
|
input?.processMouse(event: event)
|
|
}
|
|
|
|
override func rightMouseUp(with event: NSEvent) {
|
|
hasMouseDown = false
|
|
input?.processMouse(event: event)
|
|
}
|
|
|
|
override func otherMouseDown(with event: NSEvent) {
|
|
hasMouseDown = event.clickCount <= 1
|
|
input?.processMouse(event: event)
|
|
}
|
|
|
|
override func otherMouseUp(with event: NSEvent) {
|
|
hasMouseDown = false
|
|
input?.processMouse(event: event)
|
|
}
|
|
|
|
override func magnify(with event: NSEvent) {
|
|
common.window?.isAnimating = event.phase != .ended
|
|
event.phase == .ended ? common.windowDidEndLiveResize() : common.windowWillStartLiveResize()
|
|
common.window?.addWindowScale(Double(event.magnification))
|
|
}
|
|
|
|
func signalMouseMovement(_ event: NSEvent) {
|
|
var point = convert(event.locationInWindow, from: nil)
|
|
point = convertToBacking(point)
|
|
point.y = -point.y
|
|
|
|
common.window?.updateMovableBackground(point)
|
|
if !(common.window?.isMoving ?? false) {
|
|
input?.setMouse(position: point)
|
|
}
|
|
}
|
|
|
|
override func scrollWheel(with event: NSEvent) {
|
|
input?.processWheel(event: event)
|
|
}
|
|
|
|
func containsMouseLocation() -> Bool {
|
|
var topMargin: CGFloat = 0.0
|
|
let menuBarHeight = NSApp.mainMenu?.menuBarHeight ?? 23.0
|
|
|
|
guard let window = common.window else { return false }
|
|
guard var vF = window.screen?.frame else { return false }
|
|
|
|
if window.isInFullscreen && (menuBarHeight > 0) {
|
|
topMargin = TitleBar.height + 1 + menuBarHeight
|
|
}
|
|
|
|
vF.size.height -= topMargin
|
|
|
|
let vFW = window.convertFromScreen(vF)
|
|
let vFV = convert(vFW, from: nil)
|
|
let pt = convert(window.mouseLocationOutsideOfEventStream, from: nil)
|
|
|
|
var clippedBounds = bounds.intersection(vFV)
|
|
if !window.isInFullscreen {
|
|
clippedBounds.origin.y += TitleBar.height
|
|
clippedBounds.size.height -= TitleBar.height
|
|
}
|
|
return clippedBounds.contains(pt)
|
|
}
|
|
|
|
func canHideCursor() -> Bool {
|
|
guard let window = common.window else { return false }
|
|
return !hasMouseDown && containsMouseLocation() && window.isKeyWindow
|
|
}
|
|
}
|