7 Commits

Author SHA1 Message Date
Ivan Sapozhnik e976b0818f Readme, configuration element, handling content top and bottom space 2020-04-15 02:02:52 +02:00
Ivan Sapozhnik 65abdd6244 Changing from Panel to Window 2020-04-15 01:27:08 +02:00
Ivan Sapozhnik 781fb84d7a Handling dismiss 2020-04-14 23:51:27 +02:00
Ivan Sapozhnik 994681e321 Access level 2020-04-14 23:26:11 +02:00
Ivan Sapozhnik 44c7d2b415 Configurartion 2020-04-14 23:23:44 +02:00
Ivan Sapozhnik 84579bccce Access level 2020-04-14 23:18:42 +02:00
Ivan Sapozhnik fc74b68d3f Readme 2020-04-14 22:16:14 +02:00
7 changed files with 108 additions and 20 deletions
+68 -2
View File
@@ -1,5 +1,71 @@
# Menu
Fully customizible Mac OS drop-down menu
Fully customizable Mac OS drop-down menu
![](screenshot.png)
## What can be customized? Everything!
```swift
public protocol Configuration {
var titleBottomSpace: CGFloat { get }
var titleFont: NSFont? { get }
var backgroundColor: NSColor { get }
var cornerRadius: CGFloat { get }
var hasShadow: Bool { get }
var appearsBelowSender: Bool { get }
var contentEdgeInsets: NSEdgeInsets { get }
var separatorColor: NSColor { get }
var separatorThickness: CGFloat { get }
var separatorHorizontalPadding: Padding.Horizontal { get }
var separatorVerticlaPadding: Padding.Vertical { get }
var rememberSelection: Bool { get }
var textAlignment: Alignment { get }
var iconAlignment: Alignment { get }
var menuItemFont: NSFont? { get }
var menuItemHeight: CGFloat { get }
var menuItemHoverBackgroundColor: NSColor { get }
var menuItemTextColor: NSColor { get }
var menuItemHoverTextColor: NSColor { get }
var menuItemCheckmarkColor: NSColor { get }
var menuItemHoverCheckmarkColor: NSColor { get }
var menuItemCheckmarkHeight: CGFloat { get }
var menuItemCheckmarkThikness: CGFloat { get }
var menuItemImageHeight: CGFloat? { get }
var menuItemImageTintColor: NSColor? { get }
var menuItemHoverImageTintColor: NSColor? { get }
}
```
## How to use
```swift
import Cocoa
import Menu
class ViewController: NSViewController {
private let myMenu = Menu(with: "Select search engine:")
@IBAction func didClickedButton(_ sender: NSButton) {
myMenu.show(items: [
MenuItem("Bing search", image: NSImage(named: "icons8-bing-50"), action: {
sender.title = "Bing"
}),
MenuItem("DuckDuckGo search", image: NSImage(named: "icons8-duckduckgo-50"), action: {
sender.title = "DuckDuckGo"
}),
MenuItem("Google search", image: NSImage(named: "icons8-google-50"), action: {
sender.title = "Google"
}),
MenuItem.separator(),
MenuItem("Some very-very-very long text and no icon", action: {
sender.title = "Some very long text"
}),
MenuItem.separator(),
MenuItem("Exit", image: NSImage(named: "icons8-exit-50"), action: {
NSApplication.shared.terminate(nil)
})
], view: sender)
}
}
```
![](menu.png)
+4 -4
View File
@@ -57,7 +57,7 @@ class ContentViewController: NSViewController {
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: configuration.contentEdgeInsets.left),
label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -configuration.contentEdgeInsets.right),
label.topAnchor.constraint(equalTo: view.topAnchor, constant: configuration.titlePadding.top),
label.topAnchor.constraint(equalTo: view.topAnchor, constant: configuration.contentEdgeInsets.top)
])
}
@@ -67,13 +67,13 @@ class ContentViewController: NSViewController {
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -configuration.contentEdgeInsets.bottom)
])
if let titleLabel = titleLabel {
stackView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: configuration.titlePadding.bottom).isActive = true
stackView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: configuration.titleBottomSpace).isActive = true
} else {
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: configuration.contentEdgeInsets.top).isActive = true
}
menuItems.enumerated().forEach { index, item in
+17 -10
View File
@@ -43,13 +43,19 @@ public final class Menu {
fadeIn(window)
}
public func dismiss() {
public func dismiss(animated: Bool) {
let actualDismiss: (NSWindow) -> Void = { [weak self] menuWindow in
self?.window?.parent?.removeChildWindow(menuWindow)
self?.window?.orderOut(self)
self?.window = nil
}
if let menuWindow = window {
fadeOut(window: menuWindow) { [weak self] in
self?.window?.parent?.removeChildWindow(menuWindow)
self?.window?.orderOut(self)
self?.window?.close()
self?.window = nil
if animated {
fadeOut(window: menuWindow) {
actualDismiss(menuWindow)
}
} else {
actualDismiss(menuWindow)
}
}
@@ -84,7 +90,7 @@ public final class Menu {
private func setupMonitors(for parentWindow: NSWindow, targetView: NSView) {
lostFocusObserver = NotificationCenter.default.addObserver(forName: NSWindow.didResignKeyNotification, object: parentWindow, queue: nil, using: { [weak self] (_ arg1: Notification) -> Void in
self?.dismiss()
self?.dismiss(animated: false)
})
localMonitor = EventMonitor(monitorType: .local, mask: [.leftMouseDown, .rightMouseDown, .otherMouseDown], globalHandler: nil, localHandler: { [weak self] event -> NSEvent? in
@@ -92,7 +98,7 @@ public final class Menu {
if localEvent.window != self?.window {
if localEvent.window == parentWindow {
self?.dismiss()
self?.dismiss(animated: true)
// Ignore clicking on presenting view
// let contentView = parentWindow.contentView
// let locationTest = contentView?.convert(localEvent.locationInWindow, from: nil)
@@ -112,8 +118,9 @@ public final class Menu {
let presentationFrame = presentationWindow.convertToScreen(view.frame)
let presentationPoint = presentationFrame.origin
let additionalYOffset = configuration.appearsBelowSender ? 0 : NSHeight(view.frame)
let newFrame = NSRect(x: presentationPoint.x, y: presentationPoint.y - NSHeight(window.frame), width: NSWidth(view.frame), height: NSHeight(window.frame))
let newFrame = NSRect(x: presentationPoint.x, y: presentationPoint.y - NSHeight(window.frame) + additionalYOffset, width: NSWidth(view.frame), height: NSHeight(window.frame))
window.setFrame(newFrame, display: true, animate: false)
}
}
@@ -121,6 +128,6 @@ public final class Menu {
extension Menu: ContentViewControllerDelegate {
func didClickMenuElement(with index: Int) {
selectedIndex = index
dismiss()
dismiss(animated: true)
}
}
+18 -3
View File
@@ -16,11 +16,21 @@ public enum Padding {
public struct Horizontal {
let left: CGFloat
let right: CGFloat
public init(left: CGFloat, right: CGFloat) {
self.left = left
self.right = right
}
}
public struct Vertical {
let top: CGFloat
let bottom: CGFloat
public init(top: CGFloat, bottom: CGFloat) {
self.top = top
self.bottom = bottom
}
}
}
@@ -37,11 +47,12 @@ extension Padding.Vertical {
}
public protocol Configuration {
var titlePadding: Padding.Vertical { get }
var titleBottomSpace: CGFloat { get }
var titleFont: NSFont? { get }
var backgroundColor: NSColor { get }
var cornerRadius: CGFloat { get }
var hasShadow: Bool { get }
var appearsBelowSender: Bool { get }
var contentEdgeInsets: NSEdgeInsets { get }
var separatorColor: NSColor { get }
var separatorThickness: CGFloat { get }
@@ -67,8 +78,8 @@ public protocol Configuration {
open class MenuConfiguration: Configuration {
public init() {}
open var titlePadding: Padding.Vertical {
return .init(top: .grid1, bottom: .grid1)
open var titleBottomSpace: CGFloat {
return .grid1
}
open var titleFont: NSFont? {
@@ -87,6 +98,10 @@ open class MenuConfiguration: Configuration {
return true
}
open var appearsBelowSender: Bool {
return true
}
open var contentEdgeInsets: NSEdgeInsets {
return NSEdgeInsets(top: .grid2, left: .grid2, bottom: .grid2, right: .grid2)
}
+1 -1
View File
@@ -7,7 +7,7 @@
import Cocoa
final class Window: NSPanel {
final class Window: NSWindow {
private var childContentView: NSView?
private var backgroundView: RoundedRectangleView?
private let configuration: Configuration
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB