|
|
@@ -125,6 +125,34 @@ class ViewController: NSViewController {
|
|
125
|
125
|
}
|
|
126
|
126
|
}
|
|
127
|
127
|
|
|
|
128
|
+ private final class HoverTileView: NSView {
|
|
|
129
|
+ var onHoverChanged: ((Bool) -> Void)?
|
|
|
130
|
+ private var tracking: NSTrackingArea?
|
|
|
131
|
+
|
|
|
132
|
+ override func updateTrackingAreas() {
|
|
|
133
|
+ super.updateTrackingAreas()
|
|
|
134
|
+ if let tracking { removeTrackingArea(tracking) }
|
|
|
135
|
+ let area = NSTrackingArea(
|
|
|
136
|
+ rect: bounds,
|
|
|
137
|
+ options: [.activeInActiveApp, .mouseEnteredAndExited, .inVisibleRect],
|
|
|
138
|
+ owner: self,
|
|
|
139
|
+ userInfo: nil
|
|
|
140
|
+ )
|
|
|
141
|
+ addTrackingArea(area)
|
|
|
142
|
+ tracking = area
|
|
|
143
|
+ }
|
|
|
144
|
+
|
|
|
145
|
+ override func mouseEntered(with event: NSEvent) {
|
|
|
146
|
+ super.mouseEntered(with: event)
|
|
|
147
|
+ onHoverChanged?(true)
|
|
|
148
|
+ }
|
|
|
149
|
+
|
|
|
150
|
+ override func mouseExited(with event: NSEvent) {
|
|
|
151
|
+ super.mouseExited(with: event)
|
|
|
152
|
+ onHoverChanged?(false)
|
|
|
153
|
+ }
|
|
|
154
|
+ }
|
|
|
155
|
+
|
|
128
|
156
|
private var palette = Palette(isDarkMode: true)
|
|
129
|
157
|
private var darkModeEnabled: Bool {
|
|
130
|
158
|
get {
|
|
|
@@ -3752,7 +3780,7 @@ class ViewController: NSViewController {
|
|
3752
|
3780
|
}
|
|
3753
|
3781
|
|
|
3754
|
3782
|
private func makeActionTile(title: String, symbol: String, color: NSColor, action: Selector? = nil) -> NSView {
|
|
3755
|
|
- let root = NSView()
|
|
|
3783
|
+ let root = HoverTileView()
|
|
3756
|
3784
|
root.translatesAutoresizingMaskIntoConstraints = false
|
|
3757
|
3785
|
root.widthAnchor.constraint(equalToConstant: 88).isActive = true
|
|
3758
|
3786
|
// Give caption text a little more vertical room so descenders (e.g. "g") are never clipped.
|
|
|
@@ -3779,8 +3807,9 @@ class ViewController: NSViewController {
|
|
3779
|
3807
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
3780
|
3808
|
root.addSubview($0)
|
|
3781
|
3809
|
}
|
|
|
3810
|
+ let iconTopConstraint = iconButton.topAnchor.constraint(equalTo: root.topAnchor)
|
|
3782
|
3811
|
NSLayoutConstraint.activate([
|
|
3783
|
|
- iconButton.topAnchor.constraint(equalTo: root.topAnchor),
|
|
|
3812
|
+ iconTopConstraint,
|
|
3784
|
3813
|
iconButton.centerXAnchor.constraint(equalTo: root.centerXAnchor),
|
|
3785
|
3814
|
iconButton.widthAnchor.constraint(equalToConstant: 50),
|
|
3786
|
3815
|
iconButton.heightAnchor.constraint(equalToConstant: 50),
|
|
|
@@ -3788,6 +3817,18 @@ class ViewController: NSViewController {
|
|
3788
|
3817
|
label.centerXAnchor.constraint(equalTo: root.centerXAnchor),
|
|
3789
|
3818
|
label.bottomAnchor.constraint(equalTo: root.bottomAnchor)
|
|
3790
|
3819
|
])
|
|
|
3820
|
+ root.onHoverChanged = { [weak iconButton, weak label] hovering in
|
|
|
3821
|
+ NSAnimationContext.runAnimationGroup { context in
|
|
|
3822
|
+ context.duration = hovering ? 0.16 : 0.14
|
|
|
3823
|
+ context.timingFunction = CAMediaTimingFunction(name: hovering ? .easeOut : .easeInEaseOut)
|
|
|
3824
|
+ iconTopConstraint.animator().constant = hovering ? -3 : 0
|
|
|
3825
|
+ label?.animator().alphaValue = hovering ? 1.0 : 0.96
|
|
|
3826
|
+ }
|
|
|
3827
|
+ guard let iconButton else { return }
|
|
|
3828
|
+ iconButton.layer?.shadowOpacity = hovering ? 0.28 : 0.2
|
|
|
3829
|
+ iconButton.layer?.shadowRadius = hovering ? 10 : 7
|
|
|
3830
|
+ iconButton.layer?.shadowOffset = hovering ? NSSize(width: 0, height: -3) : NSSize(width: 0, height: -1)
|
|
|
3831
|
+ }
|
|
3791
|
3832
|
return root
|
|
3792
|
3833
|
}
|
|
3793
|
3834
|
|