Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 76733940b2 | |||
| a58be98a55 | |||
| 5f506926e0 | |||
| bde7399d29 | |||
| bcf6ce5e79 |
@@ -5,6 +5,7 @@
|
||||

|
||||

|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://swiftpackageindex.com/iSapozhnik/Menu)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -63,8 +64,6 @@ public protocol Configuration {
|
||||
var menuItemHoverBackgroundColor: NSColor { get }
|
||||
var menuItemTextColor: NSColor { get }
|
||||
var menuItemHoverTextColor: NSColor { get }
|
||||
var menuItemHoverCornerRadius: CGFloat { get }
|
||||
var menuItemHoverEdgeInsets: NSEdgeInsets { get }
|
||||
var menuItemCheckmarkColor: NSColor { get }
|
||||
var menuItemHoverCheckmarkColor: NSColor { get }
|
||||
var menuItemCheckmarkHeight: CGFloat { get }
|
||||
@@ -123,7 +122,7 @@ class ViewController: NSViewController {
|
||||
myMenu.addItems(menuItems)
|
||||
}
|
||||
|
||||
@IBAction func didClickedButton(_ sender: NSButton) {
|
||||
@IBAction func didClickButton(_ sender: NSButton) {
|
||||
myMenu.show(from: sender)
|
||||
}
|
||||
}
|
||||
@@ -131,12 +130,14 @@ class ViewController: NSViewController {
|
||||
|
||||
## Examples
|
||||
|
||||
In this section I've collected some examples of what can be implemented by using **Menu** control. On the left side some random examples from Dribbble and on the right side there is my implementation.
|
||||
In this section I've collected some examples of what can be i,plemented do using **Menu** control. On the left side some random example from Dribbble and on the right side my implementation.
|
||||
|
||||
| Dribbble | Menu |
|
||||
| ------------- |:-------------:|
|
||||
| Dribbble | Menu | Code |
|
||||
| ------------- |:-------------:|:-------------:|
|
||||
| [link](https://dribbble.com/shots/4233782-Snooze-notifications-in-Twist) | |
|
||||
|  |  |
|
||||
|  |  | |
|
||||
| [link](https://dribbble.com/shots/7055473-Dropdowns) | | |
|
||||
|  |  | [code](examples/examples.md) |
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
@@ -11,11 +11,14 @@ class Control: NSControl {
|
||||
var hover: ((Bool) -> Void)?
|
||||
|
||||
private let hoverLayer = CAShapeLayer()
|
||||
private let hoverColor: NSColor
|
||||
private let hoverAnimationDuration: TimeInterval
|
||||
private var trackingArea: NSTrackingArea?
|
||||
private let configuration: Configuration
|
||||
|
||||
init(with configuration: Configuration) {
|
||||
self.configuration = configuration
|
||||
self.hoverColor = configuration.menuItemHoverBackgroundColor
|
||||
self.hoverAnimationDuration = configuration.menuItemHoverAnimationDuration
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
wantsLayer = true
|
||||
@@ -30,8 +33,7 @@ class Control: NSControl {
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
let newRect = bounds.inset(by: configuration.menuItemHoverEdgeInsets)
|
||||
hoverLayer.path = CGPath(roundedRect: newRect, cornerWidth: configuration.menuItemHoverCornerRadius, cornerHeight: configuration.menuItemHoverCornerRadius, transform: nil)
|
||||
hoverLayer.path = CGPath(rect: bounds, transform: nil)
|
||||
|
||||
if let trackingArea = trackingArea, trackingAreas.contains(trackingArea) {
|
||||
removeTrackingArea(trackingArea)
|
||||
@@ -59,13 +61,13 @@ class Control: NSControl {
|
||||
|
||||
override func mouseEntered(with event: NSEvent) {
|
||||
guard isEnabled else { return }
|
||||
animateFillColor(from: .clear, to: configuration.menuItemHoverBackgroundColor)
|
||||
animateFillColor(from: .clear, to: hoverColor)
|
||||
hover?(true)
|
||||
}
|
||||
|
||||
override func mouseExited(with event: NSEvent) {
|
||||
guard isEnabled else { return }
|
||||
animateFillColor(from: configuration.menuItemHoverBackgroundColor, to: .clear)
|
||||
animateFillColor(from: hoverColor, to: .clear)
|
||||
hover?(false)
|
||||
}
|
||||
|
||||
@@ -76,7 +78,7 @@ class Control: NSControl {
|
||||
|
||||
private func animateFillColor(from oldColor: NSColor, to newColor: NSColor) {
|
||||
let animation = CABasicAnimation(keyPath: "fillColor")
|
||||
animation.duration = configuration.menuItemHoverAnimationDuration
|
||||
animation.duration = hoverAnimationDuration
|
||||
animation.fromValue = oldColor.cgColor
|
||||
animation.toValue = newColor.cgColor
|
||||
animation.fillMode = .both
|
||||
|
||||
@@ -190,14 +190,3 @@ extension CGFloat {
|
||||
/// 48 points
|
||||
static let grid6: CGFloat = 48.0
|
||||
}
|
||||
|
||||
extension NSRect {
|
||||
func inset(by insets: NSEdgeInsets) -> NSRect {
|
||||
var newRect = self
|
||||
newRect.origin.x += insets.left
|
||||
newRect.size.width -= insets.left + insets.right
|
||||
newRect.origin.y += insets.top
|
||||
newRect.size.height -= insets.top + insets.bottom
|
||||
return newRect
|
||||
}
|
||||
}
|
||||
|
||||
+2
-21
@@ -65,14 +65,6 @@ public final class Menu {
|
||||
item.action?()
|
||||
}
|
||||
|
||||
public func selectItem(withTitle title: String) {
|
||||
guard let item = items.first(where: { item -> Bool in
|
||||
item.title == title
|
||||
}) else { return }
|
||||
selectedId = item.id
|
||||
item.action?()
|
||||
}
|
||||
|
||||
// MARK: - Adding and Removing Menu Items
|
||||
public func insertItem(_ item: MenuItem, at index: Int) {
|
||||
items.insert(item, at: index)
|
||||
@@ -226,23 +218,12 @@ public final class Menu {
|
||||
guard let parentWindow = view.window, let topMostSuperView = parentWindow.contentView else { return }
|
||||
|
||||
let locationInWindow = view.convert(topMostSuperView.frame.origin, to: nil)
|
||||
let rectInWindow = NSRect(origin: locationInWindow, size: CGSize(width: NSWidth(view.frame), height: NSHeight(view.frame)))
|
||||
let rectInWindow = NSRect(origin: locationInWindow, size: CGSize(width: NSWidth(view.frame), height: NSHeight(window.frame)))
|
||||
let rectInScreen = parentWindow.convertToScreen(rectInWindow)
|
||||
let origin = rectInScreen.origin
|
||||
var additionalYOffset = configuration.appearsBelowSender ? NSHeight(view.frame) : 0
|
||||
additionalYOffset += abs(configuration.presentingOffset)
|
||||
|
||||
let newFrame: NSRect
|
||||
switch configuration.appearancePosition {
|
||||
case .rightBottom:
|
||||
newFrame = NSRect(x: origin.x, y: origin.y - NSHeight(window.frame) - additionalYOffset, width: NSWidth(view.frame), height: NSHeight(window.frame))
|
||||
case .rightTop:
|
||||
newFrame = NSRect(x: origin.x, y: origin.y + additionalYOffset, width: NSWidth(view.frame), height: NSHeight(window.frame))
|
||||
case .leftTop:
|
||||
newFrame = NSRect(x: origin.x - NSWidth(window.frame) + NSWidth(view.frame), y: origin.y - additionalYOffset, width: NSWidth(view.frame), height: NSHeight(window.frame))
|
||||
case .leftBottom:
|
||||
newFrame = NSRect(x: origin.x - NSWidth(window.frame) + NSWidth(view.frame), y: origin.y - NSHeight(window.frame) - additionalYOffset, width: NSWidth(view.frame), height: NSHeight(window.frame))
|
||||
}
|
||||
let newFrame = NSRect(x: origin.x, y: origin.y - NSHeight(window.frame) - additionalYOffset, width: NSWidth(view.frame), height: NSHeight(window.frame))
|
||||
|
||||
window.setFrame(newFrame, display: true, animate: false)
|
||||
}
|
||||
|
||||
@@ -12,13 +12,6 @@ public enum Alignment {
|
||||
case right
|
||||
}
|
||||
|
||||
public enum Position {
|
||||
case leftBottom
|
||||
case leftTop
|
||||
case rightTop
|
||||
case rightBottom
|
||||
}
|
||||
|
||||
public enum Padding {
|
||||
public struct Horizontal {
|
||||
let left: CGFloat
|
||||
@@ -61,7 +54,6 @@ public protocol Configuration {
|
||||
var cornerRadius: CGFloat { get }
|
||||
var hasShadow: Bool { get }
|
||||
var appearsBelowSender: Bool { get }
|
||||
var appearancePosition: Position { get }
|
||||
var presentingOffset: CGFloat { get }
|
||||
var animationDuration: TimeInterval { get }
|
||||
var contentEdgeInsets: NSEdgeInsets { get }
|
||||
@@ -78,8 +70,6 @@ public protocol Configuration {
|
||||
var menuItemHoverBackgroundColor: NSColor { get }
|
||||
var menuItemTextColor: NSColor { get }
|
||||
var menuItemHoverTextColor: NSColor { get }
|
||||
var menuItemHoverCornerRadius: CGFloat { get }
|
||||
var menuItemHoverEdgeInsets: NSEdgeInsets { get }
|
||||
var menuItemCheckmarkColor: NSColor { get }
|
||||
var menuItemHoverCheckmarkColor: NSColor { get }
|
||||
var menuItemCheckmarkHeight: CGFloat { get }
|
||||
@@ -122,10 +112,6 @@ open class MenuConfiguration: Configuration {
|
||||
return true
|
||||
}
|
||||
|
||||
open var appearancePosition: Position {
|
||||
return .rightBottom
|
||||
}
|
||||
|
||||
open var presentingOffset: CGFloat {
|
||||
return 0.0
|
||||
}
|
||||
@@ -186,14 +172,6 @@ open class MenuConfiguration: Configuration {
|
||||
return .white
|
||||
}
|
||||
|
||||
open var menuItemHoverEdgeInsets: NSEdgeInsets {
|
||||
return NSEdgeInsets.zero
|
||||
}
|
||||
|
||||
open var menuItemHoverCornerRadius: CGFloat {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
open var menuItemTextColor: NSColor {
|
||||
return .white
|
||||
}
|
||||
|
||||
@@ -208,12 +208,10 @@ class MenuElement: NSView {
|
||||
control.isEnabled = isEnabled
|
||||
control.hover = { [weak self] isHover in
|
||||
guard let self = self else { return }
|
||||
if self.configuration.rememberSelection {
|
||||
label.textColor = isHover ? self.configuration.menuItemHoverTextColor : isSelected ? self.configuration.menuItemHoverImageTintColor : self.configuration.menuItemTextColor
|
||||
if #available(OSX 10.14, *) {
|
||||
leftImageView?.contentTintColor = isHover ? self.configuration.menuItemHoverImageTintColor : isSelected ? self.configuration.menuItemHoverImageTintColor : self.configuration.menuItemImageTintColor
|
||||
rightImageView?.contentTintColor = isHover ? self.configuration.menuItemHoverImageTintColor : isSelected ? self.configuration.menuItemHoverImageTintColor : self.configuration.menuItemImageTintColor
|
||||
}
|
||||
label.textColor = isHover ? self.configuration.menuItemHoverTextColor : isSelected ? self.configuration.menuItemHoverImageTintColor : self.configuration.menuItemTextColor
|
||||
if #available(OSX 10.14, *) {
|
||||
leftImageView?.contentTintColor = isHover ? self.configuration.menuItemHoverImageTintColor : isSelected ? self.configuration.menuItemHoverImageTintColor : self.configuration.menuItemImageTintColor
|
||||
rightImageView?.contentTintColor = isHover ? self.configuration.menuItemHoverImageTintColor : isSelected ? self.configuration.menuItemHoverImageTintColor : self.configuration.menuItemImageTintColor
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
@@ -0,0 +1,38 @@
|
||||
## Example
|
||||
|
||||

|
||||
|
||||
### Code:
|
||||
|
||||
```
|
||||
class Config: MenuConfiguration {
|
||||
override var cornerRadius: CGFloat {
|
||||
return 15.0
|
||||
}
|
||||
|
||||
override var backgroundColor: NSColor {
|
||||
return NSColor(red: 63/255, green: 59/255, blue: 59/255, alpha: 1.0)
|
||||
}
|
||||
|
||||
override var menuItemHoverBackgroundColor: NSColor {
|
||||
return NSColor(red: 86/255, green: 81/255, blue: 81/255, alpha: 1.0)
|
||||
}
|
||||
|
||||
override var menuItemHoverCornerRadius: CGFloat {
|
||||
return 10.0
|
||||
}
|
||||
|
||||
override var contentEdgeInsets: NSEdgeInsets {
|
||||
return NSEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)
|
||||
}
|
||||
|
||||
override var menuItemHeight: CGFloat {
|
||||
return 40.0
|
||||
}
|
||||
|
||||
override var menuItemHoverEdgeInsets: NSEdgeInsets {
|
||||
return NSEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
Reference in New Issue
Block a user