Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
846a5b702a | ||
|
|
7f3a81e0e2 | ||
|
|
b08ed8e3a8 | ||
|
|
14cfd70594 |
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// EventMonitor.swift
|
||||
// Popover
|
||||
//
|
||||
// Created by Ivan Sapozhnik on 03.04.20.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class EventMonitor {
|
||||
private var monitor: Any?
|
||||
private let mask: NSEvent.EventTypeMask
|
||||
private let handler: (NSEvent?) -> Void
|
||||
|
||||
init(mask: NSEvent.EventTypeMask, handler: @escaping (NSEvent?) -> Void) {
|
||||
self.mask = mask
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
deinit {
|
||||
stop()
|
||||
}
|
||||
|
||||
func start() {
|
||||
monitor = NSEvent.addGlobalMonitorForEvents(matching: mask, handler: handler)
|
||||
}
|
||||
|
||||
func stop() {
|
||||
guard let monitor = self.monitor else { return }
|
||||
|
||||
NSEvent.removeMonitor(monitor)
|
||||
self.monitor = nil
|
||||
}
|
||||
}
|
||||
@@ -3,63 +3,61 @@
|
||||
// Popover
|
||||
//
|
||||
// Created by Ivan Sapozhnik on 29.03.20.
|
||||
// Copyright © 2020 heavylightapps. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
public class Popover: NSObject {
|
||||
private class StatusItemContainerView: NSView {
|
||||
enum Constants {
|
||||
static let maxContainerSize: CGFloat = 22.0
|
||||
static let maxContentHeight: CGFloat = 18.0
|
||||
}
|
||||
var item: NSStatusItem!
|
||||
|
||||
func embed(_ view: NSView) {
|
||||
addSubview(view)
|
||||
|
||||
let selfFrame = NSRect(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: max(NSWidth(view.bounds), Constants.maxContainerSize),
|
||||
height: Constants.maxContainerSize
|
||||
)
|
||||
let contentFrame = NSRect(
|
||||
x: 0,
|
||||
y: (Constants.maxContainerSize-Constants.maxContentHeight) / 2,
|
||||
width: NSWidth(view.bounds),
|
||||
height: Constants.maxContentHeight
|
||||
)
|
||||
|
||||
frame = selfFrame
|
||||
view.frame = contentFrame
|
||||
}
|
||||
}
|
||||
|
||||
public var item: NSStatusItem!
|
||||
|
||||
private var popoverWindowController: PopoverWindowController?
|
||||
private let windowConfiguration: PopoverConfiguration
|
||||
private var popoverWindowController: PopoverWindowController?
|
||||
private var statusItemContainer: StatusItemContainerView?
|
||||
private var eventMonitor: EventMonitor?
|
||||
|
||||
private var isPopoverWindowVisible: Bool {
|
||||
return (popoverWindowController != nil) ? popoverWindowController!.windowIsOpen : false
|
||||
}
|
||||
|
||||
/// Create a Popover with given PopoverConfiguration. Popover configuration can be either `DefaultConfiguration` or you can sublclass this class and override some of the properties.
|
||||
/// By default Popover is using Event Monitor and by doing left or right clicl ouside of the Popover's frame, the system will automatically dismiss the Popover.
|
||||
public init(with configuration: PopoverConfiguration) {
|
||||
windowConfiguration = configuration
|
||||
|
||||
super.init()
|
||||
|
||||
eventMonitor = EventMonitor(mask: [.rightMouseDown, .leftMouseDown], handler: { [weak self] event in
|
||||
guard let self = self else { return }
|
||||
self.dismiss()
|
||||
})
|
||||
}
|
||||
|
||||
public func presentPopover(with image: NSImage, contentViewController viewController: NSViewController) {
|
||||
/// Creates a Popover with given image and contentViewController
|
||||
/// By default it won't present any Popover until the user clicks on status bar item
|
||||
public func createPopover(with image: NSImage, contentViewController viewController: NSViewController) {
|
||||
configureStatusBarButton(with: image)
|
||||
popoverWindowController = PopoverWindowController(with: self, contentViewController: viewController, windowConfiguration: windowConfiguration)
|
||||
}
|
||||
|
||||
public func presentPopover(with view: NSView, contentViewController viewController: NSViewController) {
|
||||
/// Creates a Popover with given view and contentViewController. The view's height will be scaled down to 18pt and also will be vertically aligned. The wdith can be any.
|
||||
/// By default it won't present any Popover until the user clicks on status bar item
|
||||
public func createPopover(with view: NSView, contentViewController viewController: NSViewController) {
|
||||
configureStatusBarButton(with: view)
|
||||
popoverWindowController = PopoverWindowController(with: self, contentViewController: viewController, windowConfiguration: windowConfiguration)
|
||||
}
|
||||
|
||||
/// Shows the Popover with no animation
|
||||
public func show() {
|
||||
popoverWindowController?.show()
|
||||
eventMonitor?.start()
|
||||
}
|
||||
|
||||
/// Dismisses the Popover with default animation. Animation behaviour is `AnimationBehavior.utilityWindow`
|
||||
public func dismiss() {
|
||||
popoverWindowController?.dismiss()
|
||||
eventMonitor?.stop()
|
||||
}
|
||||
|
||||
private func configureStatusBarButton(with image: NSImage) {
|
||||
item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
guard let button = item.button else { return }
|
||||
@@ -88,17 +86,9 @@ public class Popover: NSObject {
|
||||
|
||||
@objc private func handleStatusItemButtonAction(_ sender: Any?) {
|
||||
if isPopoverWindowVisible {
|
||||
dismissPopoverWindow()
|
||||
dismiss()
|
||||
} else {
|
||||
showPopoverWindow()
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
public func dismissPopoverWindow() {
|
||||
popoverWindowController?.dismiss()
|
||||
}
|
||||
|
||||
public func showPopoverWindow() {
|
||||
popoverWindowController?.show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// Popover
|
||||
//
|
||||
// Created by Ivan Sapozhnik on 29.03.20.
|
||||
// Copyright © 2020 heavylightapps. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
@@ -3,17 +3,29 @@
|
||||
// Popover
|
||||
//
|
||||
// Created by Ivan Sapozhnik on 29.03.20.
|
||||
// Copyright © 2020 heavylightapps. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
public class PopoverWindow: NSPanel {
|
||||
class PopoverWindow: NSPanel {
|
||||
private let wConfig: PopoverConfiguration
|
||||
private var childContentView: NSView?
|
||||
private var backgroundView: PopoverWindowBackgroundView?
|
||||
|
||||
public static func window(with configuration: PopoverConfiguration) -> PopoverWindow {
|
||||
private init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool, configuration: PopoverConfiguration) {
|
||||
wConfig = configuration
|
||||
super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag)
|
||||
|
||||
isOpaque = false
|
||||
hasShadow = true
|
||||
level = .statusBar
|
||||
animationBehavior = .utilityWindow
|
||||
backgroundColor = .clear
|
||||
collectionBehavior = [.canJoinAllSpaces, .ignoresCycle]
|
||||
appearance = NSAppearance.current
|
||||
}
|
||||
|
||||
static func window(with configuration: PopoverConfiguration) -> PopoverWindow {
|
||||
let window = PopoverWindow.init(
|
||||
contentRect: .zero,
|
||||
styleMask: .nonactivatingPanel,
|
||||
@@ -25,11 +37,11 @@ public class PopoverWindow: NSPanel {
|
||||
return window
|
||||
}
|
||||
|
||||
public override var canBecomeKey: Bool {
|
||||
override var canBecomeKey: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
public override var contentView: NSView? {
|
||||
override var contentView: NSView? {
|
||||
set {
|
||||
guard childContentView != newValue, let bounds = newValue?.bounds else { return }
|
||||
|
||||
@@ -75,20 +87,7 @@ public class PopoverWindow: NSPanel {
|
||||
}
|
||||
}
|
||||
|
||||
public override func frameRect(forContentRect contentRect: NSRect) -> NSRect {
|
||||
override func frameRect(forContentRect contentRect: NSRect) -> NSRect {
|
||||
NSMakeRect(NSMinX(contentRect), NSMinY(contentRect), NSWidth(contentRect), NSHeight(contentRect) + wConfig.arrowHeight)
|
||||
}
|
||||
|
||||
private init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool, configuration: PopoverConfiguration) {
|
||||
wConfig = configuration
|
||||
super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag)
|
||||
|
||||
isOpaque = false
|
||||
hasShadow = true
|
||||
level = .statusBar
|
||||
animationBehavior = .utilityWindow
|
||||
backgroundColor = .clear
|
||||
collectionBehavior = [.canJoinAllSpaces, .ignoresCycle]
|
||||
appearance = NSAppearance.current
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// Popover
|
||||
//
|
||||
// Created by Ivan Sapozhnik on 29.03.20.
|
||||
// Copyright © 2020 heavylightapps. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// Popover
|
||||
//
|
||||
// Created by Ivan Sapozhnik on 29.03.20.
|
||||
// Copyright © 2020 heavylightapps. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
@@ -27,7 +26,7 @@ class PopoverWindowController: NSWindowController {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func show() {
|
||||
func show() {
|
||||
guard !isAnimating else { return }
|
||||
|
||||
updateWindowFrame()
|
||||
@@ -38,7 +37,7 @@ class PopoverWindowController: NSWindowController {
|
||||
// TODO: animation
|
||||
}
|
||||
|
||||
public func dismiss() {
|
||||
func dismiss() {
|
||||
guard !isAnimating else { return }
|
||||
|
||||
window?.orderOut(self)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// StatusItemContainerView.swift
|
||||
// Popover
|
||||
//
|
||||
// Created by Ivan Sapozhnik on 03.04.20.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class StatusItemContainerView: NSView {
|
||||
enum Constants {
|
||||
static let maxContainerSize: CGFloat = 22.0
|
||||
static let maxContentHeight: CGFloat = 18.0
|
||||
}
|
||||
|
||||
func embed(_ view: NSView) {
|
||||
addSubview(view)
|
||||
|
||||
let selfFrame = NSRect(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: max(NSWidth(view.bounds), Constants.maxContainerSize),
|
||||
height: Constants.maxContainerSize
|
||||
)
|
||||
let contentFrame = NSRect(
|
||||
x: 0,
|
||||
y: (Constants.maxContainerSize-Constants.maxContentHeight) / 2,
|
||||
width: NSWidth(view.bounds),
|
||||
height: Constants.maxContentHeight
|
||||
)
|
||||
|
||||
frame = selfFrame
|
||||
view.frame = contentFrame
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user