diff options
Diffstat (limited to 'menubar/panel.swift')
| -rw-r--r-- | menubar/panel.swift | 108 |
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 |
