From bfd7b14db76d81fd98ade3287973d22263b1b8c1 Mon Sep 17 00:00:00 2001 From: igor Date: Fri, 10 Jan 2025 22:22:07 -0800 Subject: [PATCH] Redesign of the window. --- src/Makefile | 4 +- src/PopoverPanel.swift | 12 ++-- src/SearchViewController.swift | 101 +++++++++++++++++++++++++++++---- src/ShadowView.swift | 32 +++++++++++ 4 files changed, 128 insertions(+), 21 deletions(-) create mode 100644 src/ShadowView.swift diff --git a/src/Makefile b/src/Makefile index c43876e..a6ae7c9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -11,8 +11,8 @@ SRCMODULES = Helpers.swift EditableNSTextField.swift EventMonitor.swift \ GlobalEventTap.swift PopoverPanel.swift SearchViewController.swift \ SettingsViewController.swift HotKeyManager.swift \ KeyDetectorButton.swift PathManager.swift PathsTableCellView.swift \ - ProgramTableViewCell.swift ProgramsTableView.swift AppDelegate.swift \ - main.swift + ProgramTableViewCell.swift ProgramsTableView.swift ShadowView.swift \ + AppDelegate.swift main.swift ARMOBJMODULES = $(addprefix ./arm64/,$(SRCMODULES:.swift=.o)) X86OBJMODULES = $(addprefix ./x86_64/,$(SRCMODULES:.swift=.o)) diff --git a/src/PopoverPanel.swift b/src/PopoverPanel.swift index 870c2ee..06567dc 100644 --- a/src/PopoverPanel.swift +++ b/src/PopoverPanel.swift @@ -7,20 +7,22 @@ class PopoverPanel: NSPanel { init(viewController: NSViewController) { super.init( contentRect: CGRect(x: 0, y: 0, width: 100, height: 100), - styleMask: [.titled, .nonactivatingPanel, .utilityWindow, + styleMask: [.borderless, .nonactivatingPanel, .utilityWindow, .fullSizeContentView], backing: .buffered, defer: false ) super.contentViewController = viewController + isOpaque = false + backgroundColor = NSColor.clear + hasShadow = false + title = "" isMovable = true isMovableByWindowBackground = true isFloatingPanel = true - isOpaque = false level = .statusBar - titleVisibility = .hidden titlebarAppearsTransparent = true animationBehavior = .none @@ -28,10 +30,6 @@ class PopoverPanel: NSPanel { .transient] isReleasedWhenClosed = false hidesOnDeactivate = false - - standardWindowButton(.closeButton)?.isHidden = true - standardWindowButton(.miniaturizeButton)?.isHidden = true - standardWindowButton(.zoomButton)?.isHidden = true } override func performKeyEquivalent(with event: NSEvent) -> Bool { diff --git a/src/SearchViewController.swift b/src/SearchViewController.swift index 8d4c295..2bd448b 100644 --- a/src/SearchViewController.swift +++ b/src/SearchViewController.swift @@ -1,6 +1,11 @@ import AppKit import Carbon +// NOTE: This is the corner radius of the backgrounView view that acts as +// a window frame and an NSViewController's view that clips all +// elements inside of it. +fileprivate let windowCornerRadius = 20.0 + class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDelegate, NSTableViewDataSource, NSTableViewDelegate { @@ -18,6 +23,42 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, return popover }() + private var shadowView: ShadowView = { + let view = ShadowView() + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + private var backgroundView: NSVisualEffectView = { + let effect = NSVisualEffectView() + effect.blendingMode = .behindWindow + effect.state = .active + effect.material = .popover + + effect.wantsLayer = true + effect.layer?.masksToBounds = true + + effect.layer?.borderColor = NSColor.labelColor.withAlphaComponent(0.1).cgColor + effect.layer?.borderWidth = 1 + effect.layer?.cornerRadius = windowCornerRadius + + effect.translatesAutoresizingMaskIntoConstraints = false + return effect + }() + + private var contentView: NSView = { + let view = NSView() + + // Clip all content to window's rounded frame emulated by + // backgroundView. + view.wantsLayer = true + view.layer?.masksToBounds = true + view.layer?.cornerRadius = windowCornerRadius + + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + private var appIconImage: NSImageView = { let image = NSImageView() image.image = @@ -53,7 +94,8 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, private var tableScrollView: NSScrollView = { let scroll = NSScrollView() - scroll.contentInsets = NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + scroll.contentInsets = NSEdgeInsets(top: 0, left: 0, bottom: 0, + right: 0) scroll.drawsBackground = false scroll.translatesAutoresizingMaskIntoConstraints = false return scroll @@ -81,10 +123,14 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, }() private func addSubviews() { - view.addSubview(appIconImage) - view.addSubview(searchInput) - view.addSubview(settingsButton) - view.addSubview(tableScrollView) + view.addSubview(shadowView) + view.addSubview(backgroundView) + view.addSubview(contentView) + + contentView.addSubview(appIconImage) + contentView.addSubview(searchInput) + contentView.addSubview(settingsButton) + contentView.addSubview(tableScrollView) } var viewBottomAnchorTable: NSLayoutConstraint? @@ -92,24 +138,52 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, private func setConstraints() { viewBottomAnchorTable = tableScrollView.bottomAnchor.constraint( - equalTo: view.bottomAnchor, + equalTo: contentView.bottomAnchor, constant: -ViewConstants.spacing10) viewBottomAnchorImage = appIconImage.bottomAnchor.constraint( - equalTo: view.bottomAnchor, + equalTo: contentView.bottomAnchor, constant: -ViewConstants.spacing10) viewBottomAnchorTable?.isActive = false viewBottomAnchorImage?.isActive = true NSLayoutConstraint.activate([ + view.topAnchor.constraint(equalTo: contentView.topAnchor, + constant: -100), + view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, + constant: 100), + view.leadingAnchor.constraint( + equalTo: contentView.leadingAnchor, constant: -100), + view.trailingAnchor.constraint( + equalTo: contentView.trailingAnchor, constant: 100), + + shadowView.topAnchor.constraint( + equalTo: backgroundView.topAnchor), + shadowView.bottomAnchor.constraint( + equalTo: backgroundView.bottomAnchor), + shadowView.leadingAnchor.constraint( + equalTo: backgroundView.leadingAnchor), + shadowView.trailingAnchor.constraint( + equalTo: backgroundView.trailingAnchor), + + backgroundView.topAnchor.constraint( + equalTo: contentView.topAnchor), + backgroundView.bottomAnchor.constraint( + equalTo: contentView.bottomAnchor), + backgroundView.leadingAnchor.constraint( + equalTo: contentView.leadingAnchor), + backgroundView.trailingAnchor.constraint( + equalTo: contentView.trailingAnchor), + appIconImage.widthAnchor.constraint(equalToConstant: 60), appIconImage.heightAnchor.constraint( equalTo: appIconImage.widthAnchor, multiplier: 1), - appIconImage.topAnchor.constraint(equalTo: view.topAnchor, + appIconImage.topAnchor.constraint( + equalTo: contentView.topAnchor, constant: ViewConstants.spacing10), appIconImage.leadingAnchor.constraint( - equalTo: view.leadingAnchor, + equalTo: contentView.leadingAnchor, constant: ViewConstants.spacing10), searchInput.widthAnchor.constraint(equalToConstant: 300), @@ -125,7 +199,7 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, equalTo: searchInput.trailingAnchor, constant: ViewConstants.spacing10), settingsButton.trailingAnchor.constraint( - equalTo: view.trailingAnchor, + equalTo: contentView.trailingAnchor, constant: -ViewConstants.spacing10), tableScrollView.heightAnchor.constraint(equalToConstant: 210), @@ -133,15 +207,18 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, equalTo: appIconImage.bottomAnchor, constant: ViewConstants.spacing10), tableScrollView.leadingAnchor.constraint( - equalTo: view.leadingAnchor), + equalTo: contentView.leadingAnchor), tableScrollView.trailingAnchor.constraint( - equalTo: view.trailingAnchor) + equalTo: contentView.trailingAnchor) ]) } override func viewDidLoad() { super.viewDidLoad() + view.wantsLayer = true + view.layer?.backgroundColor = NSColor.clear.cgColor + keyboardEvents = LocalEventMonitor(mask: [.keyDown], handler: { [weak self] event in let key = event.keyCode diff --git a/src/ShadowView.swift b/src/ShadowView.swift new file mode 100644 index 0000000..c990391 --- /dev/null +++ b/src/ShadowView.swift @@ -0,0 +1,32 @@ +import AppKit + +class ShadowView: NSView { + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + setupView() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupView() { + wantsLayer = true + + guard let layer = layer else { return } + + let shadow = NSShadow() + shadow.shadowColor = NSColor.black.withAlphaComponent(0.4) + shadow.shadowBlurRadius = 20.0 + shadow.shadowOffset = CGSize(width: 0, height: -10) + shadow.set() + layer.shadowPath = CGPath(rect: bounds, transform: nil) + self.shadow = shadow + } + + override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + setupView() + } +}