aboutsummaryrefslogtreecommitdiff
path: root/menubar/panel.swift
diff options
context:
space:
mode:
Diffstat (limited to 'menubar/panel.swift')
-rw-r--r--menubar/panel.swift108
1 files changed, 61 insertions, 47 deletions
diff --git a/menubar/panel.swift b/menubar/panel.swift
index 2b3cad2..003a061 100644
--- a/menubar/panel.swift
+++ b/menubar/panel.swift
@@ -147,8 +147,31 @@ private let inset: CGFloat = 8
/// desktops.
private let panelTint = mocha(30, 30, 46, 0.72)
+/// The panel's background: HUD blur under herdr's mocha base, rounded.
+func darkHUD() -> NSVisualEffectView {
+ let effect = NSVisualEffectView()
+ effect.material = .hudWindow
+ effect.blendingMode = .behindWindow
+ effect.state = .active
+ effect.wantsLayer = true
+ effect.layer?.cornerRadius = 12
+ effect.layer?.masksToBounds = true
+ let tint = NSView()
+ tint.wantsLayer = true
+ tint.layer?.backgroundColor = panelTint.cgColor
+ tint.translatesAutoresizingMaskIntoConstraints = false
+ effect.addSubview(tint)
+ NSLayoutConstraint.activate([
+ tint.leadingAnchor.constraint(equalTo: effect.leadingAnchor),
+ tint.trailingAnchor.constraint(equalTo: effect.trailingAnchor),
+ tint.topAnchor.constraint(equalTo: effect.topAnchor),
+ tint.bottomAnchor.constraint(equalTo: effect.bottomAnchor),
+ ])
+ return effect
+}
+
/// A click target that does not swallow drags: a drag moves the panel.
-private class ClickView: NSView {
+class ClickView: NSView {
var onClick: () -> Void = {}
private var dragged = false
var hovered = false { didSet { needsDisplay = true; hoverChanged() } }
@@ -185,7 +208,6 @@ private class ClickView: NSView {
}
override func mouseUp(with event: NSEvent) {
- log("panel: mouse up (dragged: \(dragged))")
if !dragged { onClick() }
}
}
@@ -210,12 +232,16 @@ private func singleLine(_ label: NSTextField) -> NSTextField {
}
/// One agent row: colored symbol and name, then a dim subtitle.
-private final class PanelRowView: ClickView {
- init(row: Row, onClick: @escaping (String) -> Void) {
+final class PanelRowView: ClickView {
+ private let selected: Bool
+
+ init(row: Row, title titleText: NSAttributedString? = nil, selected: Bool = false,
+ onClick: @escaping (String) -> Void) {
+ self.selected = selected
super.init(frame: .zero)
self.onClick = { onClick(row.paneId) }
let title = singleLine(NSTextField(
- labelWithAttributedString: rowTitle(row, primary: mochaText, secondary: mochaSubtext)))
+ labelWithAttributedString: titleText ?? rowTitle(row, primary: mochaText, secondary: mochaSubtext)))
let subtitleLabel = NSTextField(labelWithString: row.subtitle)
subtitleLabel.font = .systemFont(ofSize: 11)
subtitleLabel.textColor = mochaSubtext
@@ -240,8 +266,8 @@ private final class PanelRowView: ClickView {
required init?(coder: NSCoder) { fatalError("not used") }
override func draw(_ dirtyRect: NSRect) {
- guard hovered else { return }
- NSColor.white.withAlphaComponent(0.08).setFill()
+ guard hovered || selected else { return }
+ NSColor.white.withAlphaComponent(selected ? 0.14 : 0.08).setFill()
NSBezierPath(roundedRect: bounds, xRadius: 6, yRadius: 6).fill()
}
}
@@ -294,31 +320,15 @@ final class PanelController {
panel.hasShadow = true
panel.appearance = NSAppearance(named: .darkAqua)
- let effect = NSVisualEffectView()
- effect.material = .hudWindow
- effect.blendingMode = .behindWindow
- effect.state = .active
- effect.wantsLayer = true
- effect.layer?.cornerRadius = 12
- effect.layer?.masksToBounds = true
+ let effect = darkHUD()
panel.contentView = effect
- let tint = NSView()
- tint.wantsLayer = true
- tint.layer?.backgroundColor = panelTint.cgColor
- tint.translatesAutoresizingMaskIntoConstraints = false
- effect.addSubview(tint)
-
stack.orientation = .vertical
stack.alignment = .leading
stack.spacing = 0
stack.translatesAutoresizingMaskIntoConstraints = false
effect.addSubview(stack)
NSLayoutConstraint.activate([
- tint.leadingAnchor.constraint(equalTo: effect.leadingAnchor),
- tint.trailingAnchor.constraint(equalTo: effect.trailingAnchor),
- tint.topAnchor.constraint(equalTo: effect.topAnchor),
- tint.bottomAnchor.constraint(equalTo: effect.bottomAnchor),
stack.leadingAnchor.constraint(equalTo: effect.leadingAnchor, constant: inset),
stack.trailingAnchor.constraint(equalTo: effect.trailingAnchor, constant: -inset),
stack.topAnchor.constraint(equalTo: effect.topAnchor, constant: inset),
@@ -420,32 +430,36 @@ final class PanelController {
func show() { panel.orderFrontRegardless() }
func hide() { panel.orderOut(nil) }
- /// Debug: draw the panel offscreen over a white and a black backdrop,
- /// side by side, as a PNG.
func snapshot(to path: String) -> Bool {
guard let view = panel.contentView else { return false }
- view.layoutSubtreeIfNeeded()
- guard let rep = view.bitmapImageRepForCachingDisplay(in: view.bounds) else { return false }
- view.cacheDisplay(in: view.bounds, to: rep)
- let size = view.bounds.size
- let image = NSImage(size: NSSize(width: size.width * 2 + 20, height: size.height))
- image.lockFocus()
- NSColor.white.setFill()
- NSRect(x: 0, y: 0, width: size.width + 10, height: size.height).fill()
- NSColor.black.setFill()
- NSRect(x: size.width + 10, y: 0, width: size.width + 10, height: size.height).fill()
- // the blur does not render offscreen: approximate it with a mid gray
- for x in [CGFloat(0), size.width + 20] {
- NSColor(white: 0.5, alpha: 0.5).setFill()
- NSBezierPath(roundedRect: NSRect(x: x, y: 0, width: size.width, height: size.height),
- xRadius: 12, yRadius: 12).fill()
- rep.draw(in: NSRect(x: x, y: 0, width: size.width, height: size.height))
- }
- image.unlockFocus()
- guard let tiff = image.tiffRepresentation, let bitmap = NSBitmapImageRep(data: tiff),
- let png = bitmap.representation(using: .png, properties: [:]) else { return false }
- return (try? png.write(to: URL(fileURLWithPath: path))) != nil
+ return snapshotView(view, to: path)
+ }
+}
+
+/// Debug: draw a dark HUD view offscreen over a white and a black backdrop,
+/// side by side, as a PNG.
+func snapshotView(_ view: NSView, to path: String) -> Bool {
+ view.layoutSubtreeIfNeeded()
+ guard let rep = view.bitmapImageRepForCachingDisplay(in: view.bounds) else { return false }
+ view.cacheDisplay(in: view.bounds, to: rep)
+ let size = view.bounds.size
+ let image = NSImage(size: NSSize(width: size.width * 2 + 20, height: size.height))
+ image.lockFocus()
+ NSColor.white.setFill()
+ NSRect(x: 0, y: 0, width: size.width + 10, height: size.height).fill()
+ NSColor.black.setFill()
+ NSRect(x: size.width + 10, y: 0, width: size.width + 10, height: size.height).fill()
+ // the blur does not render offscreen: approximate it with a mid gray
+ for x in [CGFloat(0), size.width + 20] {
+ NSColor(white: 0.5, alpha: 0.5).setFill()
+ NSBezierPath(roundedRect: NSRect(x: x, y: 0, width: size.width, height: size.height),
+ xRadius: 12, yRadius: 12).fill()
+ rep.draw(in: NSRect(x: x, y: 0, width: size.width, height: size.height))
}
+ image.unlockFocus()
+ guard let tiff = image.tiffRepresentation, let bitmap = NSBitmapImageRep(data: tiff),
+ let png = bitmap.representation(using: .png, properties: [:]) else { return false }
+ return (try? png.write(to: URL(fileURLWithPath: path))) != nil
}
// MARK: selftest