mirror of
https://github.com/insidegui/WWDC.git
synced 2026-04-07 19:17:41 +00:00
222 lines
6.3 KiB
Swift
Executable File
222 lines
6.3 KiB
Swift
Executable File
//
|
|
// PUIButton.swift
|
|
// PlayerUI
|
|
//
|
|
// Created by Guilherme Rambo on 29/04/17.
|
|
// Copyright © 2017 Guilherme Rambo. All rights reserved.
|
|
//
|
|
|
|
import Cocoa
|
|
import SwiftUI
|
|
import AVKit
|
|
|
|
public final class PUIButton: NSControl, ObservableObject {
|
|
|
|
public override init(frame frameRect: NSRect) {
|
|
super.init(frame: frameRect)
|
|
|
|
setup()
|
|
}
|
|
|
|
public required init?(coder: NSCoder) {
|
|
fatalError()
|
|
}
|
|
|
|
public var isToggle = false
|
|
|
|
var isAVRoutePickerMasquerade = false {
|
|
didSet {
|
|
guard isAVRoutePickerMasquerade != oldValue else { return }
|
|
setupAVRoutePicker()
|
|
}
|
|
}
|
|
|
|
@Published public var activeTintColor: NSColor = .playerHighlight
|
|
|
|
@Published public var tintColor: NSColor = .buttonColor
|
|
|
|
@Published public var state: NSControl.StateValue = .off
|
|
|
|
public var showsMenuOnLeftClick = false
|
|
public var showsMenuOnRightClick = false
|
|
public var sendsActionOnMouseDown = false
|
|
|
|
@Published public var image: NSImage?
|
|
|
|
@Published public var alternateImage: NSImage?
|
|
|
|
@Published var metrics = PUIControlMetrics.medium
|
|
|
|
@Published fileprivate var shouldDrawHighlighted: Bool = false
|
|
|
|
@Published public var shouldAlwaysDrawHighlighted: Bool = false
|
|
|
|
public override var isEnabled: Bool {
|
|
didSet {
|
|
objectWillChange.send()
|
|
}
|
|
}
|
|
|
|
private func setup() {
|
|
let host = PUIFirstMouseHostingView(rootView: PUIButtonContent(button: self))
|
|
host.translatesAutoresizingMaskIntoConstraints = false
|
|
addSubview(host)
|
|
NSLayoutConstraint.activate([
|
|
host.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
host.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
host.topAnchor.constraint(equalTo: topAnchor),
|
|
host.bottomAnchor.constraint(equalTo: bottomAnchor)
|
|
])
|
|
}
|
|
|
|
public override func mouseDown(with event: NSEvent) {
|
|
guard isEnabled else { return }
|
|
|
|
guard !showsMenuOnLeftClick else {
|
|
showMenu(with: event)
|
|
return
|
|
}
|
|
|
|
shouldDrawHighlighted = true
|
|
|
|
if !sendsActionOnMouseDown {
|
|
window?.trackEvents(matching: [.leftMouseUp, .leftMouseDragged], timeout: NSEvent.foreverDuration, mode: .eventTracking) { event, stop in
|
|
if event?.type == .leftMouseUp {
|
|
self.shouldDrawHighlighted = false
|
|
stop.pointee = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if let action = action, let target = target {
|
|
if isToggle {
|
|
state = (state == .on) ? .off : .on
|
|
}
|
|
NSApp.sendAction(action, to: target, from: self)
|
|
}
|
|
}
|
|
|
|
public override func rightMouseDown(with event: NSEvent) {
|
|
guard showsMenuOnRightClick else {
|
|
return
|
|
}
|
|
showMenu(with: event)
|
|
}
|
|
|
|
private func showMenu(with event: NSEvent) {
|
|
guard let menu = menu else { return }
|
|
|
|
menu.popUp(positioning: nil, at: .zero, in: self)
|
|
}
|
|
|
|
public override var allowsVibrancy: Bool { true }
|
|
|
|
public override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true }
|
|
|
|
// MARK: - AVRoutePickerView
|
|
|
|
var player: AVPlayer? {
|
|
get { routePicker.player }
|
|
set { routePicker.player = newValue }
|
|
}
|
|
|
|
private lazy var routePicker: AVRoutePickerView = {
|
|
let v = AVRoutePickerView()
|
|
v.setRoutePickerButtonColor(.buttonColor, for: .normal)
|
|
v.translatesAutoresizingMaskIntoConstraints = false
|
|
return v
|
|
}()
|
|
|
|
private var installedRoutePicker: AVRoutePickerView?
|
|
|
|
private func setupAVRoutePicker() {
|
|
guard isAVRoutePickerMasquerade else {
|
|
installedRoutePicker?.removeFromSuperview()
|
|
return
|
|
}
|
|
|
|
addSubview(routePicker)
|
|
NSLayoutConstraint.activate([
|
|
routePicker.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
routePicker.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
routePicker.topAnchor.constraint(equalTo: topAnchor),
|
|
routePicker.bottomAnchor.constraint(equalTo: bottomAnchor)
|
|
])
|
|
|
|
// Hack alert
|
|
routePicker.alphaValue = 0.01
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - SwiftUI Content
|
|
|
|
private struct PUIButtonContent: View {
|
|
@ObservedObject var button: PUIButton
|
|
|
|
private var currentImage: Image? {
|
|
if let alternateImage = button.alternateImage, button.state == .on {
|
|
return Image(nsImage: alternateImage.withPlayerMetrics(button.metrics))
|
|
} else if let image = button.image {
|
|
return Image(nsImage: image.withPlayerMetrics(button.metrics))
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private var foregroundColor: Color {
|
|
guard !button.shouldAlwaysDrawHighlighted else { return Color(nsColor: button.activeTintColor) }
|
|
return Color(nsColor: button.state == .on ? button.activeTintColor : button.tintColor)
|
|
}
|
|
|
|
private var opacity: CGFloat {
|
|
guard button.isEnabled else { return 0.5 }
|
|
|
|
guard !button.shouldAlwaysDrawHighlighted else { return 1.0 }
|
|
|
|
return button.shouldDrawHighlighted ? 0.8 : 1.0
|
|
}
|
|
|
|
private var scale: CGFloat {
|
|
guard button.isEnabled, !button.shouldAlwaysDrawHighlighted else { return 1 }
|
|
|
|
return button.shouldDrawHighlighted ? 0.9 : 1.0
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
if button.isToggle {
|
|
glyph
|
|
.id(button.state)
|
|
.transition(.scale(scale: 0.2).combined(with: .opacity))
|
|
} else {
|
|
glyph
|
|
}
|
|
}
|
|
.animation(.snappy(extraBounce: button.state == .on ? 0.25 : 0), value: button.state)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var glyph: some View {
|
|
if let currentImage {
|
|
currentImage
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fit)
|
|
.frame(width: button.metrics.controlSize, height: button.metrics.controlSize)
|
|
.foregroundColor(foregroundColor)
|
|
.opacity(opacity)
|
|
.scaleEffect(scale)
|
|
}
|
|
}
|
|
}
|
|
|
|
final class PUIFirstMouseButton: NSButton {
|
|
override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true }
|
|
}
|
|
|
|
private final class PUIFirstMouseHostingView<RootView: View>: NSHostingView<RootView> {
|
|
|
|
override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true }
|
|
|
|
}
|