Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1f68383d8 | ||
|
|
9f92012270 | ||
|
|
dee3296f9e | ||
|
|
b30566900b | ||
|
|
53771db7fe | ||
|
|
08efd190ee | ||
|
|
dc5346fbda | ||
|
|
664d0c881b | ||
|
|
7f62847149 | ||
|
|
0e47c6f786 | ||
|
|
7d2b8bc923 | ||
|
|
168b893236 | ||
|
|
c95768cc8a | ||
|
|
d4743915c2 | ||
|
|
cde8f5a3ad |
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Sapozhnik Ivan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,6 +1,15 @@
|
||||
# Popover
|
||||
|
||||
Mac OS custom popover.
|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
</div>
|
||||
|
||||
>Custom macOS popover.
|
||||
|
||||

|
||||
|
||||
# Install
|
||||
@@ -12,7 +21,7 @@ Update your `Package.swift` dependencies:
|
||||
|
||||
```
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/iSapozhnik/Popover", from: "1.0.8")
|
||||
.package(url: "https://github.com/iSapozhnik/Popover", from: "1.1.1")
|
||||
]
|
||||
```
|
||||
|
||||
@@ -28,7 +37,7 @@ dependencies: [
|
||||
2. Create a view you want to put into a status bar.
|
||||
3. Create a content view controller which would be embedded inside Popover
|
||||
4. Optionally create menu items
|
||||
5. Create a Popover instance. Here you can use either standard configuration (you don't need to pass `windowConfiguration` parameter in this case) or you can subclass `DefaultConfiguration`, override some properties and pass a new instance as a parameter. If in ste 4 you have created menu items, pass it here as well.
|
||||
5. Create a Popover instance. Here you can use either standard configuration (you don't need to pass `windowConfiguration` parameter in this case) or you can subclass `DefaultConfiguration`, override some properties and pass a new instance as a parameter. If in step 4 you have created menu items, pass it here as well. You can see them by making right click on menu bar item.
|
||||
6. Call `prepare` to set everything up.
|
||||
|
||||
And here is how typical `AppDelegate` may look like:
|
||||
@@ -112,5 +121,17 @@ public protocol PopoverConfiguration {
|
||||
}
|
||||
```
|
||||
|
||||
## Mentions
|
||||
- [AppCoda Weekly - Issue #172](http://digest.appcoda.com/issues/appcoda-weekly-issue-172-239907)
|
||||
|
||||
## Credits
|
||||
|
||||
Created and maintained by [**@iSapozhnik**](https://twitter.com/iSapozhnik).
|
||||
|
||||
## License
|
||||
|
||||
Released under the MIT License. See `LICENSE` for details.
|
||||
|
||||
>**Copyright © 2020-present Sapozhnik Ivan.**
|
||||
|
||||
Heavily inspired by [CCNStatusItem](https://github.com/phranck/CCNStatusItem)
|
||||
|
||||
@@ -8,7 +8,13 @@
|
||||
import Cocoa
|
||||
|
||||
public class Popover: NSObject {
|
||||
var item: NSStatusItem! {
|
||||
public var window: NSWindow? {
|
||||
return popoverWindowController?.window
|
||||
}
|
||||
|
||||
public var keepPopoverVisible: Bool = false
|
||||
|
||||
@objc dynamic var item: NSStatusItem! {
|
||||
didSet {
|
||||
prepareMenu()
|
||||
}
|
||||
@@ -34,7 +40,7 @@ public class Popover: NSObject {
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - windowConfiguration: Popover window configuration. Subclass `DefaultConfiguration` and override properties or conform to `PopoverConfiguration` protocol.
|
||||
public convenience override init(with windowConfiguration: PopoverConfiguration) {
|
||||
public convenience init(with windowConfiguration: PopoverConfiguration) {
|
||||
self.init(with: windowConfiguration, menuItems: nil)
|
||||
}
|
||||
|
||||
@@ -80,11 +86,20 @@ public class Popover: NSObject {
|
||||
localEventMonitor?.start()
|
||||
}
|
||||
|
||||
/// Shows the Popover with no animation
|
||||
public func show() {
|
||||
self.show(withFocus: true)
|
||||
}
|
||||
|
||||
/// Shows the Popover with no animation
|
||||
public func show(withFocus: Bool) {
|
||||
guard !isPopoverWindowVisible else { return }
|
||||
popoverWindowController?.show()
|
||||
popoverWindowController?.show(withFocus: withFocus)
|
||||
globalEventMonitor?.start()
|
||||
|
||||
guard let button = item.button else { return }
|
||||
DispatchQueue.main.async(execute: {
|
||||
button.isHighlighted = true
|
||||
})
|
||||
}
|
||||
|
||||
/// Dismisses the Popover with default animation. Animation behavior is `AnimationBehavior.utilityWindow`
|
||||
@@ -92,12 +107,19 @@ public class Popover: NSObject {
|
||||
guard isPopoverWindowVisible else { return }
|
||||
popoverWindowController?.dismiss()
|
||||
globalEventMonitor?.stop()
|
||||
|
||||
guard let button = item.button else { return }
|
||||
DispatchQueue.main.async(execute: {
|
||||
button.isHighlighted = false
|
||||
})
|
||||
}
|
||||
|
||||
private func setupMonitors() {
|
||||
globalEventMonitor = EventMonitor(monitorType: .global, mask: [.leftMouseDown, .rightMouseDown], globalHandler: { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.dismiss()
|
||||
if (!self.keepPopoverVisible) {
|
||||
self.dismiss()
|
||||
}
|
||||
}, localHandler: nil)
|
||||
|
||||
if menuItems != nil, menuItems?.isNotEmpty ?? false {
|
||||
@@ -117,6 +139,14 @@ public class Popover: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
private func removeMonitors() {
|
||||
globalEventMonitor?.stop()
|
||||
globalEventMonitor = nil
|
||||
|
||||
localEventMonitor?.stop()
|
||||
localEventMonitor = nil
|
||||
}
|
||||
|
||||
private func configureStatusBarButton(with image: NSImage) {
|
||||
item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
guard let button = item.button else { return }
|
||||
@@ -139,7 +169,7 @@ public class Popover: NSObject {
|
||||
}
|
||||
|
||||
@objc private func handleStatusItemButtonAction(_ sender: Any?) {
|
||||
isPopoverWindowVisible ? dismiss() : show()
|
||||
isPopoverWindowVisible ? dismiss() : show(withFocus: true)
|
||||
}
|
||||
|
||||
private func setTargetAction(for button: NSButton) {
|
||||
|
||||
@@ -44,7 +44,7 @@ class PopoverWindow: NSPanel {
|
||||
static func window(with configuration: PopoverConfiguration) -> PopoverWindow {
|
||||
let window = PopoverWindow.init(
|
||||
contentRect: .zero,
|
||||
styleMask: .nonactivatingPanel,
|
||||
styleMask: [.nonactivatingPanel],
|
||||
backing: .buffered,
|
||||
defer: true,
|
||||
configuration: configuration
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import Cocoa
|
||||
|
||||
class PopoverWindowController: NSWindowController {
|
||||
class PopoverWindowController: NSWindowController, NSWindowDelegate {
|
||||
public private(set) var windowIsOpen: Bool = false
|
||||
public private(set) var isAnimating: Bool = false
|
||||
|
||||
@@ -18,7 +18,9 @@ class PopoverWindowController: NSWindowController {
|
||||
self.popover = popover
|
||||
self.wConfig = windowConfiguration
|
||||
|
||||
super.init(window: PopoverWindow.window(with: windowConfiguration))
|
||||
let window = PopoverWindow.window(with: windowConfiguration)
|
||||
super.init(window: window)
|
||||
window.delegate = self
|
||||
self.contentViewController = contentViewController
|
||||
}
|
||||
|
||||
@@ -26,14 +28,18 @@ class PopoverWindowController: NSWindowController {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func show() {
|
||||
func show(withFocus: Bool) {
|
||||
guard !isAnimating else { return }
|
||||
|
||||
updateWindowFrame()
|
||||
window?.alphaValue = 1.0
|
||||
showWindow(nil)
|
||||
|
||||
if (withFocus) {
|
||||
showWindow(nil)
|
||||
window?.makeKey()
|
||||
} else {
|
||||
window?.orderFrontRegardless()
|
||||
}
|
||||
windowIsOpen = true
|
||||
window?.makeKey()
|
||||
// TODO: animation
|
||||
}
|
||||
|
||||
@@ -45,7 +51,7 @@ class PopoverWindowController: NSWindowController {
|
||||
windowIsOpen = false
|
||||
}
|
||||
|
||||
private func updateWindowFrame() {
|
||||
func updateWindowFrame() {
|
||||
guard let window = self.window as? PopoverWindow else { return }
|
||||
let windowFrame = visibleStatusItemWindowFrame()
|
||||
window.arrowXLocation = windowFrame.arrowXLocation
|
||||
@@ -82,4 +88,8 @@ class PopoverWindowController: NSWindowController {
|
||||
|
||||
return currentScreen ?? NSScreen.main ?? NSScreen()
|
||||
}
|
||||
|
||||
func windowDidResize(_ notification: Notification) {
|
||||
updateWindowFrame()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user