|
@@ -629,7 +629,7 @@ private extension ViewController {
|
|
629
|
}
|
629
|
}
|
|
630
|
|
630
|
|
|
631
|
/// Ensures `NSClickGestureRecognizer` on the row receives clicks instead of child label/image views swallowing them.
|
631
|
/// Ensures `NSClickGestureRecognizer` on the row receives clicks instead of child label/image views swallowing them.
|
|
632
|
-private final class RowHitTestView: NSView {
|
|
|
|
|
|
632
|
+private class RowHitTestView: NSView {
|
|
633
|
override func hitTest(_ point: NSPoint) -> NSView? {
|
633
|
override func hitTest(_ point: NSPoint) -> NSView? {
|
|
634
|
guard let superview else { return nil }
|
634
|
guard let superview else { return nil }
|
|
635
|
let local = convert(point, from: superview)
|
635
|
let local = convert(point, from: superview)
|
|
@@ -637,6 +637,50 @@ private final class RowHitTestView: NSView {
|
|
637
|
}
|
637
|
}
|
|
638
|
}
|
638
|
}
|
|
639
|
|
639
|
|
|
|
|
640
|
+private final class HoverTrackingView: RowHitTestView {
|
|
|
|
641
|
+ var onHoverChanged: ((Bool) -> Void)?
|
|
|
|
642
|
+ var showsHandCursor = true
|
|
|
|
643
|
+
|
|
|
|
644
|
+ private var trackingAreaRef: NSTrackingArea?
|
|
|
|
645
|
+ private var isHovering = false {
|
|
|
|
646
|
+ didSet {
|
|
|
|
647
|
+ guard isHovering != oldValue else { return }
|
|
|
|
648
|
+ onHoverChanged?(isHovering)
|
|
|
|
649
|
+ }
|
|
|
|
650
|
+ }
|
|
|
|
651
|
+
|
|
|
|
652
|
+ override func updateTrackingAreas() {
|
|
|
|
653
|
+ super.updateTrackingAreas()
|
|
|
|
654
|
+ if let trackingAreaRef {
|
|
|
|
655
|
+ removeTrackingArea(trackingAreaRef)
|
|
|
|
656
|
+ }
|
|
|
|
657
|
+ let options: NSTrackingArea.Options = [
|
|
|
|
658
|
+ .activeInKeyWindow,
|
|
|
|
659
|
+ .inVisibleRect,
|
|
|
|
660
|
+ .mouseEnteredAndExited
|
|
|
|
661
|
+ ]
|
|
|
|
662
|
+ let area = NSTrackingArea(rect: bounds, options: options, owner: self, userInfo: nil)
|
|
|
|
663
|
+ addTrackingArea(area)
|
|
|
|
664
|
+ trackingAreaRef = area
|
|
|
|
665
|
+ }
|
|
|
|
666
|
+
|
|
|
|
667
|
+ override func mouseEntered(with event: NSEvent) {
|
|
|
|
668
|
+ super.mouseEntered(with: event)
|
|
|
|
669
|
+ isHovering = true
|
|
|
|
670
|
+ }
|
|
|
|
671
|
+
|
|
|
|
672
|
+ override func mouseExited(with event: NSEvent) {
|
|
|
|
673
|
+ super.mouseExited(with: event)
|
|
|
|
674
|
+ isHovering = false
|
|
|
|
675
|
+ }
|
|
|
|
676
|
+
|
|
|
|
677
|
+ override func resetCursorRects() {
|
|
|
|
678
|
+ super.resetCursorRects()
|
|
|
|
679
|
+ guard showsHandCursor else { return }
|
|
|
|
680
|
+ addCursorRect(bounds, cursor: .pointingHand)
|
|
|
|
681
|
+ }
|
|
|
|
682
|
+}
|
|
|
|
683
|
+
|
|
640
|
private final class SettingsMenuViewController: NSViewController {
|
684
|
private final class SettingsMenuViewController: NSViewController {
|
|
641
|
private let palette: Palette
|
685
|
private let palette: Palette
|
|
642
|
private let typography: Typography
|
686
|
private let typography: Typography
|
|
@@ -727,7 +771,7 @@ private final class SettingsMenuViewController: NSViewController {
|
|
727
|
}
|
771
|
}
|
|
728
|
|
772
|
|
|
729
|
private func settingsDarkModeRow(enabled: Bool) -> NSView {
|
773
|
private func settingsDarkModeRow(enabled: Bool) -> NSView {
|
|
730
|
- let row = RowHitTestView()
|
|
|
|
|
|
774
|
+ let row = HoverTrackingView()
|
|
731
|
row.translatesAutoresizingMaskIntoConstraints = false
|
775
|
row.translatesAutoresizingMaskIntoConstraints = false
|
|
732
|
row.heightAnchor.constraint(equalToConstant: 44).isActive = true
|
776
|
row.heightAnchor.constraint(equalToConstant: 44).isActive = true
|
|
733
|
|
777
|
|
|
@@ -751,6 +795,12 @@ private final class SettingsMenuViewController: NSViewController {
|
|
751
|
row.addSubview(icon)
|
795
|
row.addSubview(icon)
|
|
752
|
row.addSubview(title)
|
796
|
row.addSubview(title)
|
|
753
|
row.addSubview(toggle)
|
797
|
row.addSubview(toggle)
|
|
|
|
798
|
+ row.onHoverChanged = { hovering in
|
|
|
|
799
|
+ row.wantsLayer = true
|
|
|
|
800
|
+ row.layer?.cornerRadius = 10
|
|
|
|
801
|
+ row.layer?.backgroundColor = (hovering ? NSColor(calibratedWhite: 1, alpha: 0.06) : NSColor.clear).cgColor
|
|
|
|
802
|
+ }
|
|
|
|
803
|
+ row.onHoverChanged?(false)
|
|
754
|
|
804
|
|
|
755
|
NSLayoutConstraint.activate([
|
805
|
NSLayoutConstraint.activate([
|
|
756
|
icon.leadingAnchor.constraint(equalTo: row.leadingAnchor, constant: 4),
|
806
|
icon.leadingAnchor.constraint(equalTo: row.leadingAnchor, constant: 4),
|
|
@@ -767,7 +817,7 @@ private final class SettingsMenuViewController: NSViewController {
|
|
767
|
}
|
817
|
}
|
|
768
|
|
818
|
|
|
769
|
private func settingsActionRow(icon: String, title: String, action: SettingsAction) -> NSView {
|
819
|
private func settingsActionRow(icon: String, title: String, action: SettingsAction) -> NSView {
|
|
770
|
- let row = RowHitTestView()
|
|
|
|
|
|
820
|
+ let row = HoverTrackingView()
|
|
771
|
row.translatesAutoresizingMaskIntoConstraints = false
|
821
|
row.translatesAutoresizingMaskIntoConstraints = false
|
|
772
|
row.heightAnchor.constraint(equalToConstant: 42).isActive = true
|
822
|
row.heightAnchor.constraint(equalToConstant: 42).isActive = true
|
|
773
|
|
823
|
|
|
@@ -794,6 +844,12 @@ private final class SettingsMenuViewController: NSViewController {
|
|
794
|
let click = NSClickGestureRecognizer(target: self, action: #selector(settingsActionClicked(_:)))
|
844
|
let click = NSClickGestureRecognizer(target: self, action: #selector(settingsActionClicked(_:)))
|
|
795
|
row.addGestureRecognizer(click)
|
845
|
row.addGestureRecognizer(click)
|
|
796
|
row.identifier = NSUserInterfaceItemIdentifier(rawValue: "\(action.rawValue)")
|
846
|
row.identifier = NSUserInterfaceItemIdentifier(rawValue: "\(action.rawValue)")
|
|
|
|
847
|
+ row.onHoverChanged = { hovering in
|
|
|
|
848
|
+ row.wantsLayer = true
|
|
|
|
849
|
+ row.layer?.cornerRadius = 10
|
|
|
|
850
|
+ row.layer?.backgroundColor = (hovering ? NSColor(calibratedWhite: 1, alpha: 0.06) : NSColor.clear).cgColor
|
|
|
|
851
|
+ }
|
|
|
|
852
|
+ row.onHoverChanged?(false)
|
|
797
|
|
853
|
|
|
798
|
return row
|
854
|
return row
|
|
799
|
}
|
855
|
}
|
|
@@ -852,7 +908,7 @@ private extension ViewController {
|
|
852
|
}
|
908
|
}
|
|
853
|
|
909
|
|
|
854
|
func sidebarItem(_ text: String, icon: String, page: SidebarPage, logoImageName: String? = nil, logoIconWidth: CGFloat = 18, logoHeightMultiplier: CGFloat = 1, logoTemplate: Bool = true, showsDisclosure: Bool = false) -> NSView {
|
910
|
func sidebarItem(_ text: String, icon: String, page: SidebarPage, logoImageName: String? = nil, logoIconWidth: CGFloat = 18, logoHeightMultiplier: CGFloat = 1, logoTemplate: Bool = true, showsDisclosure: Bool = false) -> NSView {
|
|
855
|
- let item = RowHitTestView()
|
|
|
|
|
|
911
|
+ let item = HoverTrackingView()
|
|
856
|
item.wantsLayer = true
|
912
|
item.wantsLayer = true
|
|
857
|
item.layer?.cornerRadius = 10
|
913
|
item.layer?.cornerRadius = 10
|
|
858
|
item.layer?.backgroundColor = NSColor.clear.cgColor
|
914
|
item.layer?.backgroundColor = NSColor.clear.cgColor
|
|
@@ -910,6 +966,10 @@ private extension ViewController {
|
|
910
|
NSLayoutConstraint.activate(constraints)
|
966
|
NSLayoutConstraint.activate(constraints)
|
|
911
|
|
967
|
|
|
912
|
applySidebarRowStyle(item, page: page, logoTemplate: logoTemplate)
|
968
|
applySidebarRowStyle(item, page: page, logoTemplate: logoTemplate)
|
|
|
|
969
|
+ item.onHoverChanged = { [weak self, weak item] hovering in
|
|
|
|
970
|
+ guard let self, let item else { return }
|
|
|
|
971
|
+ self.applySidebarRowStyle(item, page: page, logoTemplate: logoTemplate, hovering: hovering)
|
|
|
|
972
|
+ }
|
|
913
|
|
973
|
|
|
914
|
let click = NSClickGestureRecognizer(target: self, action: #selector(sidebarItemClicked(_:)))
|
974
|
let click = NSClickGestureRecognizer(target: self, action: #selector(sidebarItemClicked(_:)))
|
|
915
|
item.addGestureRecognizer(click)
|
975
|
item.addGestureRecognizer(click)
|
|
@@ -917,9 +977,10 @@ private extension ViewController {
|
|
917
|
return item
|
977
|
return item
|
|
918
|
}
|
978
|
}
|
|
919
|
|
979
|
|
|
920
|
- func applySidebarRowStyle(_ item: NSView, page: SidebarPage, logoTemplate: Bool) {
|
|
|
|
|
|
980
|
+ func applySidebarRowStyle(_ item: NSView, page: SidebarPage, logoTemplate: Bool, hovering: Bool = false) {
|
|
921
|
let selected = (page == selectedSidebarPage)
|
981
|
let selected = (page == selectedSidebarPage)
|
|
922
|
- item.layer?.backgroundColor = (selected ? palette.primaryBlue : NSColor.clear).cgColor
|
|
|
|
|
|
982
|
+ let hoverColor = NSColor(calibratedWhite: 1, alpha: 0.07)
|
|
|
|
983
|
+ item.layer?.backgroundColor = (selected ? palette.primaryBlue : (hovering ? hoverColor : NSColor.clear)).cgColor
|
|
923
|
let tint = selected ? NSColor.white : palette.textSecondary
|
984
|
let tint = selected ? NSColor.white : palette.textSecondary
|
|
924
|
guard item.subviews.count >= 2 else { return }
|
985
|
guard item.subviews.count >= 2 else { return }
|
|
925
|
let leading = item.subviews[0]
|
986
|
let leading = item.subviews[0]
|
|
@@ -939,7 +1000,7 @@ private extension ViewController {
|
|
939
|
}
|
1000
|
}
|
|
940
|
|
1001
|
|
|
941
|
func topTab(_ title: String, icon: String, provider: MeetingProvider, logoImageName: String? = nil, logoPointSize: CGFloat = 26, logoHeightMultiplier: CGFloat = 1, logoTemplate: Bool = true) -> NSView {
|
1002
|
func topTab(_ title: String, icon: String, provider: MeetingProvider, logoImageName: String? = nil, logoPointSize: CGFloat = 26, logoHeightMultiplier: CGFloat = 1, logoTemplate: Bool = true) -> NSView {
|
|
942
|
- let tab = RowHitTestView()
|
|
|
|
|
|
1003
|
+ let tab = HoverTrackingView()
|
|
943
|
tab.wantsLayer = true
|
1004
|
tab.wantsLayer = true
|
|
944
|
tab.layer?.cornerRadius = 19
|
1005
|
tab.layer?.cornerRadius = 19
|
|
945
|
tab.layer?.backgroundColor = NSColor.clear.cgColor
|
1006
|
tab.layer?.backgroundColor = NSColor.clear.cgColor
|
|
@@ -981,6 +1042,10 @@ private extension ViewController {
|
|
981
|
NSLayoutConstraint.activate(constraints)
|
1042
|
NSLayoutConstraint.activate(constraints)
|
|
982
|
|
1043
|
|
|
983
|
applyTabStyle(tab, provider: provider, logoTemplate: logoTemplate)
|
1044
|
applyTabStyle(tab, provider: provider, logoTemplate: logoTemplate)
|
|
|
|
1045
|
+ tab.onHoverChanged = { [weak self, weak tab] hovering in
|
|
|
|
1046
|
+ guard let self, let tab else { return }
|
|
|
|
1047
|
+ self.applyTabStyle(tab, provider: provider, logoTemplate: logoTemplate, hovering: hovering)
|
|
|
|
1048
|
+ }
|
|
984
|
|
1049
|
|
|
985
|
let click = NSClickGestureRecognizer(target: self, action: #selector(meetingTabClicked(_:)))
|
1050
|
let click = NSClickGestureRecognizer(target: self, action: #selector(meetingTabClicked(_:)))
|
|
986
|
tab.addGestureRecognizer(click)
|
1051
|
tab.addGestureRecognizer(click)
|
|
@@ -988,9 +1053,10 @@ private extension ViewController {
|
|
988
|
return tab
|
1053
|
return tab
|
|
989
|
}
|
1054
|
}
|
|
990
|
|
1055
|
|
|
991
|
- func applyTabStyle(_ tab: NSView, provider: MeetingProvider, logoTemplate: Bool) {
|
|
|
|
|
|
1056
|
+ func applyTabStyle(_ tab: NSView, provider: MeetingProvider, logoTemplate: Bool, hovering: Bool = false) {
|
|
992
|
let selected = (provider == selectedMeetingProvider)
|
1057
|
let selected = (provider == selectedMeetingProvider)
|
|
993
|
- tab.layer?.backgroundColor = (selected ? palette.primaryBlue : NSColor.clear).cgColor
|
|
|
|
|
|
1058
|
+ let hoverColor = NSColor(calibratedWhite: 1, alpha: 0.07)
|
|
|
|
1059
|
+ tab.layer?.backgroundColor = (selected ? palette.primaryBlue : (hovering ? hoverColor : NSColor.clear)).cgColor
|
|
994
|
guard tab.subviews.count >= 2 else { return }
|
1060
|
guard tab.subviews.count >= 2 else { return }
|
|
995
|
let leading = tab.subviews[0]
|
1061
|
let leading = tab.subviews[0]
|
|
996
|
let title = tab.subviews[1] as? NSTextField
|
1062
|
let title = tab.subviews[1] as? NSTextField
|
|
@@ -1006,7 +1072,10 @@ private extension ViewController {
|
|
1006
|
}
|
1072
|
}
|
|
1007
|
|
1073
|
|
|
1008
|
func actionButton(title: String, color: NSColor, textColor: NSColor, width: CGFloat) -> NSView {
|
1074
|
func actionButton(title: String, color: NSColor, textColor: NSColor, width: CGFloat) -> NSView {
|
|
1009
|
- let button = roundedContainer(cornerRadius: 9, color: color)
|
|
|
|
|
|
1075
|
+ let button = HoverTrackingView()
|
|
|
|
1076
|
+ button.wantsLayer = true
|
|
|
|
1077
|
+ button.layer?.cornerRadius = 9
|
|
|
|
1078
|
+ button.layer?.backgroundColor = color.cgColor
|
|
1010
|
button.translatesAutoresizingMaskIntoConstraints = false
|
1079
|
button.translatesAutoresizingMaskIntoConstraints = false
|
|
1011
|
button.widthAnchor.constraint(equalToConstant: width).isActive = true
|
1080
|
button.widthAnchor.constraint(equalToConstant: width).isActive = true
|
|
1012
|
button.heightAnchor.constraint(equalToConstant: 36).isActive = true
|
1081
|
button.heightAnchor.constraint(equalToConstant: 36).isActive = true
|
|
@@ -1022,11 +1091,21 @@ private extension ViewController {
|
|
1022
|
label.centerYAnchor.constraint(equalTo: button.centerYAnchor)
|
1091
|
label.centerYAnchor.constraint(equalTo: button.centerYAnchor)
|
|
1023
|
])
|
1092
|
])
|
|
1024
|
|
1093
|
|
|
|
|
1094
|
+ let baseColor = (title == "Cancel") ? palette.cancelButton : color
|
|
|
|
1095
|
+ let hoverColor = baseColor.blended(withFraction: 0.12, of: NSColor.white) ?? baseColor
|
|
|
|
1096
|
+ button.onHoverChanged = { hovering in
|
|
|
|
1097
|
+ button.layer?.backgroundColor = (hovering ? hoverColor : baseColor).cgColor
|
|
|
|
1098
|
+ }
|
|
|
|
1099
|
+ button.onHoverChanged?(false)
|
|
|
|
1100
|
+
|
|
1025
|
return button
|
1101
|
return button
|
|
1026
|
}
|
1102
|
}
|
|
1027
|
|
1103
|
|
|
1028
|
func iconRoundButton(_ symbol: String, size: CGFloat) -> NSView {
|
1104
|
func iconRoundButton(_ symbol: String, size: CGFloat) -> NSView {
|
|
1029
|
- let button = roundedContainer(cornerRadius: size / 2, color: palette.inputBackground)
|
|
|
|
|
|
1105
|
+ let button = HoverTrackingView()
|
|
|
|
1106
|
+ button.wantsLayer = true
|
|
|
|
1107
|
+ button.layer?.cornerRadius = size / 2
|
|
|
|
1108
|
+ button.layer?.backgroundColor = palette.inputBackground.cgColor
|
|
1030
|
button.translatesAutoresizingMaskIntoConstraints = false
|
1109
|
button.translatesAutoresizingMaskIntoConstraints = false
|
|
1031
|
button.widthAnchor.constraint(equalToConstant: size).isActive = true
|
1110
|
button.widthAnchor.constraint(equalToConstant: size).isActive = true
|
|
1032
|
button.heightAnchor.constraint(equalToConstant: size).isActive = true
|
1111
|
button.heightAnchor.constraint(equalToConstant: size).isActive = true
|
|
@@ -1039,6 +1118,13 @@ private extension ViewController {
|
|
1039
|
label.centerYAnchor.constraint(equalTo: button.centerYAnchor)
|
1118
|
label.centerYAnchor.constraint(equalTo: button.centerYAnchor)
|
|
1040
|
])
|
1119
|
])
|
|
1041
|
|
1120
|
|
|
|
|
1121
|
+ let baseColor = palette.inputBackground
|
|
|
|
1122
|
+ let hoverColor = baseColor.blended(withFraction: 0.10, of: NSColor.white) ?? baseColor
|
|
|
|
1123
|
+ button.onHoverChanged = { hovering in
|
|
|
|
1124
|
+ button.layer?.backgroundColor = (hovering ? hoverColor : baseColor).cgColor
|
|
|
|
1125
|
+ }
|
|
|
|
1126
|
+ button.onHoverChanged?(false)
|
|
|
|
1127
|
+
|
|
1042
|
return button
|
1128
|
return button
|
|
1043
|
}
|
1129
|
}
|
|
1044
|
}
|
1130
|
}
|