Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f51f80c5e0 | ||
|
|
bb87a2dc3a | ||
|
|
f39f238846 |
@@ -10,7 +10,7 @@ import Cocoa
|
||||
public class Popover: NSObject {
|
||||
var item: NSStatusItem!
|
||||
|
||||
private let windowConfiguration: PopoverConfiguration
|
||||
private let wConfig: PopoverConfiguration
|
||||
private var popoverWindowController: PopoverWindowController?
|
||||
private var statusItemContainer: StatusItemContainerView?
|
||||
private var eventMonitor: EventMonitor?
|
||||
@@ -19,10 +19,10 @@ public class Popover: NSObject {
|
||||
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
|
||||
/// Creates 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 click ouside of the Popover's frame, the Popover will be automatically dismissed.
|
||||
public init(with windowConfiguration: PopoverConfiguration) {
|
||||
wConfig = windowConfiguration
|
||||
|
||||
super.init()
|
||||
|
||||
@@ -32,18 +32,23 @@ public class Popover: NSObject {
|
||||
})
|
||||
}
|
||||
|
||||
/// Convenience initializer which is using default configuration
|
||||
public convenience override init() {
|
||||
self.init(with: DefaultConfiguration())
|
||||
}
|
||||
|
||||
/// 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)
|
||||
popoverWindowController = PopoverWindowController(with: self, contentViewController: viewController, windowConfiguration: wConfig)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
popoverWindowController = PopoverWindowController(with: self, contentViewController: viewController, windowConfiguration: wConfig)
|
||||
}
|
||||
|
||||
/// Shows the Popover with no animation
|
||||
@@ -85,10 +90,6 @@ public class Popover: NSObject {
|
||||
}
|
||||
|
||||
@objc private func handleStatusItemButtonAction(_ sender: Any?) {
|
||||
if isPopoverWindowVisible {
|
||||
dismiss()
|
||||
} else {
|
||||
show()
|
||||
}
|
||||
isPopoverWindowVisible ? dismiss() : show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,40 @@
|
||||
import Cocoa
|
||||
|
||||
public protocol PopoverConfiguration {
|
||||
var popoverToStatusItemMargin: CGFloat { get }
|
||||
var backgroundColor: NSColor { get }
|
||||
var borderColor: NSColor? { get }
|
||||
var borderWidth: CGFloat { get }
|
||||
var arrowHeight: CGFloat { get }
|
||||
var arrowWidth: CGFloat { get }
|
||||
var cornerRadius: CGFloat { get }
|
||||
var contentInset: NSEdgeInsets { get }
|
||||
/// The distance from Popover's arrow to a status item.
|
||||
var popoverToStatusItemMargin: CGFloat { get }
|
||||
|
||||
/// Popover's background color.
|
||||
var backgroundColor: NSColor { get }
|
||||
|
||||
/// Popover's border color.
|
||||
/// - Important:
|
||||
/// If `borderColor` returns `nil`, settings `borderWidth` won't make any affect. See also: `borderWidth`.
|
||||
var borderColor: NSColor? { get }
|
||||
|
||||
/// Popover's border width.
|
||||
/// - Important:
|
||||
/// If Popover's border color is set to `nil`, setting `borderWidth` won't make any affect.
|
||||
var borderWidth: CGFloat { get }
|
||||
|
||||
/// Defines Popover arrow height.
|
||||
var arrowHeight: CGFloat { get }
|
||||
|
||||
/// Defines Popover arrow width.
|
||||
var arrowWidth: CGFloat { get }
|
||||
|
||||
/// Defines Popover corner radius.
|
||||
/// - Warning:
|
||||
/// If this value is too big and if the Popover's status item (menu bar view) is too close to the right edge, the appearence of the Popover might be odd.
|
||||
var cornerRadius: CGFloat { get }
|
||||
|
||||
/// Defines Popover content edge insets.
|
||||
var contentEdgeInsets: NSEdgeInsets { get }
|
||||
|
||||
/// The distance from the right side of the Popover to the screen's edge.
|
||||
/// - Warning:
|
||||
/// If this value is too big and if the Popover's status item (menu bar view) is too close to the right edge, the appearence of the Popover might be odd.
|
||||
var rightEdgeMargin: CGFloat { get }
|
||||
}
|
||||
|
||||
open class DefaultConfiguration: PopoverConfiguration {
|
||||
@@ -46,10 +72,14 @@ open class DefaultConfiguration: PopoverConfiguration {
|
||||
}
|
||||
|
||||
open var cornerRadius: CGFloat {
|
||||
return 12
|
||||
return 5
|
||||
}
|
||||
|
||||
open var contentInset: NSEdgeInsets {
|
||||
open var contentEdgeInsets: NSEdgeInsets {
|
||||
return NSEdgeInsetsZero
|
||||
}
|
||||
|
||||
open var rightEdgeMargin: CGFloat {
|
||||
return 12.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
import Cocoa
|
||||
|
||||
class PopoverWindow: NSPanel {
|
||||
var arrowXLocation: CGFloat = 0.0 {
|
||||
didSet {
|
||||
updateArrowPosition()
|
||||
}
|
||||
}
|
||||
private let wConfig: PopoverConfiguration
|
||||
private var childContentView: NSView?
|
||||
private var backgroundView: PopoverWindowBackgroundView?
|
||||
@@ -69,10 +74,10 @@ class PopoverWindow: NSPanel {
|
||||
backgroundView.addSubview(userContentView)
|
||||
|
||||
let borderWidth = wConfig.borderColor != nil ? wConfig.borderWidth : 0
|
||||
let left = borderWidth + wConfig.contentInset.left
|
||||
let right = borderWidth + wConfig.contentInset.right
|
||||
let top = wConfig.arrowHeight + borderWidth + wConfig.contentInset.top
|
||||
let bottom = borderWidth + wConfig.contentInset.bottom
|
||||
let left = borderWidth + wConfig.contentEdgeInsets.left
|
||||
let right = borderWidth + wConfig.contentEdgeInsets.right
|
||||
let top = wConfig.arrowHeight + borderWidth + wConfig.contentEdgeInsets.top
|
||||
let bottom = borderWidth + wConfig.contentEdgeInsets.bottom
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
userContentView.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor, constant: left),
|
||||
@@ -90,4 +95,9 @@ class PopoverWindow: NSPanel {
|
||||
override func frameRect(forContentRect contentRect: NSRect) -> NSRect {
|
||||
NSMakeRect(NSMinX(contentRect), NSMinY(contentRect), NSWidth(contentRect), NSHeight(contentRect) + wConfig.arrowHeight)
|
||||
}
|
||||
|
||||
private func updateArrowPosition() {
|
||||
guard let backgroundView = self.backgroundView as? PopoverWindowBackgroundView else { return }
|
||||
backgroundView.arrowXLocation = arrowXLocation
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@ import Cocoa
|
||||
|
||||
class PopoverWindowBackgroundView: NSView {
|
||||
private let wConfig: PopoverConfiguration
|
||||
var arrowXLocation: CGFloat = 0.0 {
|
||||
didSet {
|
||||
needsDisplay = true
|
||||
}
|
||||
}
|
||||
|
||||
init(frame frameRect: NSRect, windowConfiguration: PopoverConfiguration) {
|
||||
self.wConfig = windowConfiguration
|
||||
@@ -35,13 +40,14 @@ class PopoverWindowBackgroundView: NSView {
|
||||
let arrowPath = NSBezierPath()
|
||||
let backgroundPath = NSBezierPath(roundedRect: backgroundRect, xRadius: wConfig.cornerRadius, yRadius: wConfig.cornerRadius)
|
||||
|
||||
let leftPoint = NSPoint(x: NSWidth(backgroundRect) / 2 - wConfig.arrowWidth / 2, y: NSMaxY(backgroundRect))
|
||||
let toPoint = NSPoint(x: NSWidth(backgroundRect) / 2, y: NSMaxY(backgroundRect) + wConfig.arrowHeight)
|
||||
let rightPoint = NSPoint(x: NSWidth(backgroundRect) / 2 + wConfig.arrowWidth / 2, y: NSMaxY(backgroundRect))
|
||||
let cp11 = NSPoint(x: NSWidth(backgroundRect) / 2 - wConfig.arrowWidth / 6, y: NSMaxY(backgroundRect))
|
||||
let cp12 = NSPoint(x: NSWidth(backgroundRect) / 2 - wConfig.arrowWidth / 9, y: NSMaxY(backgroundRect) + wConfig.arrowHeight)
|
||||
let cp21 = NSPoint(x: NSWidth(backgroundRect) / 2 + wConfig.arrowWidth / 9, y: NSMaxY(backgroundRect) + wConfig.arrowHeight)
|
||||
let cp22 = NSPoint(x: NSWidth(backgroundRect) / 2 + wConfig.arrowWidth / 6, y: NSMaxY(backgroundRect))
|
||||
let x = arrowXLocation
|
||||
let leftPoint = NSPoint(x: x - wConfig.arrowWidth / 2, y: NSMaxY(backgroundRect))
|
||||
let toPoint = NSPoint(x: x, y: NSMaxY(backgroundRect) + wConfig.arrowHeight)
|
||||
let rightPoint = NSPoint(x: x + wConfig.arrowWidth / 2, y: NSMaxY(backgroundRect))
|
||||
let cp11 = NSPoint(x: x - wConfig.arrowWidth / 6, y: NSMaxY(backgroundRect))
|
||||
let cp12 = NSPoint(x: x - wConfig.arrowWidth / 9, y: NSMaxY(backgroundRect) + wConfig.arrowHeight)
|
||||
let cp21 = NSPoint(x: x + wConfig.arrowWidth / 9, y: NSMaxY(backgroundRect) + wConfig.arrowHeight)
|
||||
let cp22 = NSPoint(x: x + wConfig.arrowWidth / 6, y: NSMaxY(backgroundRect))
|
||||
|
||||
controlPoints.append(NSBezierPath(ovalIn: NSMakeRect(leftPoint.x - 2, leftPoint.y - 2, 4, 4)))
|
||||
controlPoints.append(NSBezierPath(ovalIn: NSMakeRect(toPoint.x - 2, toPoint.y - 2, 4, 4)))
|
||||
@@ -70,8 +76,8 @@ class PopoverWindowBackgroundView: NSView {
|
||||
wConfig.backgroundColor.setFill()
|
||||
windowPath.fill()
|
||||
|
||||
// NSColor.red.setFill()
|
||||
// controlPoints.fill()
|
||||
|
||||
}
|
||||
|
||||
override var frame: NSRect {
|
||||
|
||||
@@ -46,27 +46,28 @@ class PopoverWindowController: NSWindowController {
|
||||
}
|
||||
|
||||
private func updateWindowFrame() {
|
||||
guard let window = self.window as? PopoverWindow else { return }
|
||||
let windowFrame = visibleStatusItemWindowFrame()
|
||||
window?.setFrame(windowFrame, display: true)
|
||||
window?.appearance = NSAppearance.current
|
||||
window.arrowXLocation = windowFrame.arrowXLocation
|
||||
window.setFrame(windowFrame.frame, display: true)
|
||||
window.appearance = NSAppearance.current
|
||||
}
|
||||
|
||||
private func visibleStatusItemWindowFrame() -> NSRect {
|
||||
private func visibleStatusItemWindowFrame() -> (frame: NSRect, arrowXLocation: CGFloat) {
|
||||
let screenFrame = currentMouseScreen().frame
|
||||
let popoverRect = popover.item.button?.window?.frame ?? .zero
|
||||
let statusItemRect = popover.item.button?.window?.frame ?? .zero
|
||||
|
||||
guard let window = self.window else { return .zero }
|
||||
guard let window = self.window else { return (.zero, 0.0) }
|
||||
|
||||
let borderWidth = wConfig.borderColor != nil ? wConfig.borderWidth : 0
|
||||
let x = NSMinX(popoverRect) - NSWidth(window.frame) / 2 + NSWidth(popoverRect) / 2 + borderWidth
|
||||
let y = min(NSMinY(popoverRect), NSMaxY(screenFrame)) - NSHeight(window.frame) - wConfig.popoverToStatusItemMargin + borderWidth / 2
|
||||
let x = min(NSMinX(statusItemRect) - NSWidth(window.frame) / 2 + NSWidth(statusItemRect) / 2 + borderWidth, NSMaxX(screenFrame) - NSWidth(window.frame) - wConfig.rightEdgeMargin)
|
||||
let y = min(NSMinY(statusItemRect), NSMaxY(screenFrame)) - NSHeight(window.frame) - wConfig.popoverToStatusItemMargin + borderWidth / 2
|
||||
|
||||
return NSMakeRect(
|
||||
x,
|
||||
y,
|
||||
NSWidth(window.frame),
|
||||
NSHeight(window.frame)
|
||||
)
|
||||
let windowFrame = NSMakeRect(x, y, NSWidth(window.frame), NSHeight(window.frame))
|
||||
|
||||
let arrowXLocation = NSMinX(statusItemRect) + NSWidth(statusItemRect) / 2 - NSMinX(windowFrame)
|
||||
|
||||
return (windowFrame, arrowXLocation)
|
||||
}
|
||||
|
||||
private func currentMouseScreen() -> NSScreen {
|
||||
|
||||
Reference in New Issue
Block a user