style: add sunset palette and soft background glows
This commit is contained in:
@@ -37,9 +37,9 @@ Search is case- and accent-insensitive. Ranking is exact name, prefix, substring
|
|||||||
|
|
||||||
## Native UI
|
## Native UI
|
||||||
|
|
||||||
The launcher now follows the Raycast reference: a 750 × 474-point, titlebar-free dark panel with rounded corners, native translucency and shadow, a large borderless search field, compact icon rows, neutral rounded selection, right-aligned application labels, and a bottom-right launch pill with a Return key hint. It deliberately uses dark appearance for this initial visual direction.
|
The launcher keeps its Raycast-inspired 750 × 474-point, titlebar-free rounded panel, large borderless search field, compact icon rows, right-aligned application labels, and bottom-right launch pill. Its Sunset palette uses a dark brown background, soft amber and rust glows, warm cream text, and orange accents on the caret, selection, and action outline.
|
||||||
|
|
||||||
AppKit still provides text editing, table navigation, scrolling, application icons, controls, menus, fonts, semantic colors, and visual-effect materials. Custom styling is limited to layout, rounded surfaces, and selection drawing; increased-contrast settings add a selection outline. No third-party UI dependencies, copied Raycast branding, or nonfunctional Actions menu.
|
AppKit still provides text editing, table navigation, scrolling, application icons, controls, menus, fonts, and visual-effect materials. The background uses cached native radial gradients that fade to transparent: static soft blobs, without animations, timers, image assets, blur filters, or third-party UI dependencies. Decorative drawing does not intercept clicks. Increased-contrast settings strengthen the selection outline, and text colors are tuned for readability across the glows and selection.
|
||||||
|
|
||||||
- `Sources/Conjure`: application lifecycle, native window, and application launching.
|
- `Sources/Conjure`: application lifecycle, native window, and application launching.
|
||||||
- `Sources/ConjureKit`: filesystem discovery and search ranking.
|
- `Sources/ConjureKit`: filesystem discovery and search ranking.
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import AppKit
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
enum SunsetPalette {
|
||||||
|
static let background = NSColor(srgbRed: 0.12, green: 0.09, blue: 0.08, alpha: 0.96)
|
||||||
|
static let text = NSColor(srgbRed: 1.00, green: 0.89, blue: 0.71, alpha: 1)
|
||||||
|
static let secondaryText = NSColor(srgbRed: 0.85, green: 0.78, blue: 0.65, alpha: 1)
|
||||||
|
static let accent = NSColor(srgbRed: 0.95, green: 0.58, blue: 0.23, alpha: 1)
|
||||||
|
static let selectionFill = accent.withAlphaComponent(0.12)
|
||||||
|
static let selectionBorder = accent.withAlphaComponent(0.30)
|
||||||
|
static let ember = NSColor(srgbRed: 0.76, green: 0.27, blue: 0.18, alpha: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
final class LauncherBackground: NSView {
|
||||||
|
private let amber = NSGradient(colors: [
|
||||||
|
SunsetPalette.accent.withAlphaComponent(0.18),
|
||||||
|
SunsetPalette.accent.withAlphaComponent(0.065),
|
||||||
|
SunsetPalette.accent.withAlphaComponent(0),
|
||||||
|
])!
|
||||||
|
private let ember = NSGradient(colors: [
|
||||||
|
SunsetPalette.ember.withAlphaComponent(0.26),
|
||||||
|
SunsetPalette.ember.withAlphaComponent(0.10),
|
||||||
|
SunsetPalette.ember.withAlphaComponent(0),
|
||||||
|
])!
|
||||||
|
|
||||||
|
override func hitTest(_ point: NSPoint) -> NSView? { nil }
|
||||||
|
|
||||||
|
override func draw(_ dirtyRect: NSRect) {
|
||||||
|
SunsetPalette.background.setFill()
|
||||||
|
dirtyRect.fill()
|
||||||
|
let upperGlow = NSPoint(x: bounds.width * 0.58, y: bounds.height * 0.94)
|
||||||
|
ember.draw(fromCenter: upperGlow, radius: 0, toCenter: upperGlow,
|
||||||
|
radius: bounds.width * 0.40, options: [])
|
||||||
|
let lowerGlow = NSPoint(x: bounds.width * 0.88, y: bounds.height * 0.10)
|
||||||
|
amber.draw(fromCenter: lowerGlow, radius: 0, toCenter: lowerGlow,
|
||||||
|
radius: bounds.width * 0.55, options: [])
|
||||||
|
let leftGlow = NSPoint(x: bounds.width * 0.08, y: bounds.height * 0.65)
|
||||||
|
NSGraphicsContext.saveGraphicsState()
|
||||||
|
NSGraphicsContext.current?.cgContext.setAlpha(0.45)
|
||||||
|
amber.draw(fromCenter: leftGlow, radius: 0, toCenter: leftGlow,
|
||||||
|
radius: bounds.width * 0.32, options: [])
|
||||||
|
NSGraphicsContext.restoreGraphicsState()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,7 +47,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
|||||||
NSApp.activate()
|
NSApp.activate()
|
||||||
window?.makeKeyAndOrderFront(nil)
|
window?.makeKeyAndOrderFront(nil)
|
||||||
window?.makeFirstResponder(searchField)
|
window?.makeFirstResponder(searchField)
|
||||||
(searchField.currentEditor() as? NSTextView)?.insertionPointColor = .labelColor
|
(searchField.currentEditor() as? NSTextView)?.insertionPointColor = SunsetPalette.accent
|
||||||
refreshApplications()
|
refreshApplications()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,14 +66,18 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
|||||||
content.layer?.masksToBounds = true
|
content.layer?.masksToBounds = true
|
||||||
content.layer?.borderWidth = 0.5
|
content.layer?.borderWidth = 0.5
|
||||||
window.contentView = content
|
window.contentView = content
|
||||||
let tint = NSView(frame: content.bounds)
|
let background = LauncherBackground(frame: content.bounds)
|
||||||
tint.autoresizingMask = [.width, .height]
|
background.autoresizingMask = [.width, .height]
|
||||||
tint.wantsLayer = true
|
background.setAccessibilityElement(false)
|
||||||
content.addSubview(tint)
|
content.addSubview(background)
|
||||||
|
|
||||||
searchField.placeholderString = "Search for apps…"
|
let searchFont = NSFont.systemFont(ofSize: 20)
|
||||||
searchField.font = .systemFont(ofSize: 20)
|
searchField.font = searchFont
|
||||||
searchField.textColor = .labelColor
|
searchField.placeholderAttributedString = NSAttributedString(
|
||||||
|
string: "Search for apps…",
|
||||||
|
attributes: [.foregroundColor: SunsetPalette.secondaryText, .font: searchFont]
|
||||||
|
)
|
||||||
|
searchField.textColor = SunsetPalette.text
|
||||||
searchField.isBordered = false
|
searchField.isBordered = false
|
||||||
searchField.drawsBackground = false
|
searchField.drawsBackground = false
|
||||||
searchField.focusRingType = .none
|
searchField.focusRingType = .none
|
||||||
@@ -88,7 +92,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
|||||||
|
|
||||||
let symbol = NSImageView()
|
let symbol = NSImageView()
|
||||||
symbol.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: nil)
|
symbol.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: nil)
|
||||||
symbol.contentTintColor = .tertiaryLabelColor
|
symbol.contentTintColor = SunsetPalette.accent
|
||||||
symbol.setAccessibilityElement(false)
|
symbol.setAccessibilityElement(false)
|
||||||
let header = NSStackView(views: [symbol, searchField])
|
let header = NSStackView(views: [symbol, searchField])
|
||||||
header.orientation = .horizontal
|
header.orientation = .horizontal
|
||||||
@@ -97,7 +101,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
|||||||
|
|
||||||
let sectionLabel = NSTextField(labelWithString: "Applications")
|
let sectionLabel = NSTextField(labelWithString: "Applications")
|
||||||
sectionLabel.font = .systemFont(ofSize: 12, weight: .semibold)
|
sectionLabel.font = .systemFont(ofSize: 12, weight: .semibold)
|
||||||
sectionLabel.textColor = .secondaryLabelColor
|
sectionLabel.textColor = SunsetPalette.secondaryText
|
||||||
|
|
||||||
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("application"))
|
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("application"))
|
||||||
column.resizingMask = .autoresizingMask
|
column.resizingMask = .autoresizingMask
|
||||||
@@ -134,14 +138,14 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
|||||||
scrollView.horizontalScrollElasticity = .none
|
scrollView.horizontalScrollElasticity = .none
|
||||||
|
|
||||||
statusLabel.font = .systemFont(ofSize: 12)
|
statusLabel.font = .systemFont(ofSize: 12)
|
||||||
statusLabel.textColor = .secondaryLabelColor
|
statusLabel.textColor = SunsetPalette.secondaryText
|
||||||
statusLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
statusLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||||
emptyLabel.textColor = .secondaryLabelColor
|
emptyLabel.textColor = SunsetPalette.secondaryText
|
||||||
emptyLabel.alignment = .center
|
emptyLabel.alignment = .center
|
||||||
|
|
||||||
openButton.isBordered = false
|
openButton.isBordered = false
|
||||||
openButton.font = .systemFont(ofSize: 14, weight: .medium)
|
openButton.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
openButton.contentTintColor = .labelColor
|
openButton.contentTintColor = SunsetPalette.text
|
||||||
openButton.keyEquivalent = "\r"
|
openButton.keyEquivalent = "\r"
|
||||||
openButton.target = self
|
openButton.target = self
|
||||||
openButton.action = #selector(openSelection)
|
openButton.action = #selector(openSelection)
|
||||||
@@ -149,7 +153,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
|||||||
openButton.toolTip = "Open the selected application (Return). Use ↑ and ↓ to select."
|
openButton.toolTip = "Open the selected application (Return). Use ↑ and ↓ to select."
|
||||||
let returnKey = NSTextField(labelWithString: "↵")
|
let returnKey = NSTextField(labelWithString: "↵")
|
||||||
returnKey.font = .systemFont(ofSize: 14)
|
returnKey.font = .systemFont(ofSize: 14)
|
||||||
returnKey.textColor = .secondaryLabelColor
|
returnKey.textColor = SunsetPalette.secondaryText
|
||||||
returnKey.alignment = .center
|
returnKey.alignment = .center
|
||||||
returnKey.wantsLayer = true
|
returnKey.wantsLayer = true
|
||||||
returnKey.layer?.cornerRadius = 5
|
returnKey.layer?.cornerRadius = 5
|
||||||
@@ -208,11 +212,11 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
|||||||
returnKey.heightAnchor.constraint(equalToConstant: 18),
|
returnKey.heightAnchor.constraint(equalToConstant: 18),
|
||||||
])
|
])
|
||||||
window.effectiveAppearance.performAsCurrentDrawingAppearance {
|
window.effectiveAppearance.performAsCurrentDrawingAppearance {
|
||||||
let border = NSColor.separatorColor.cgColor
|
let border = SunsetPalette.secondaryText.withAlphaComponent(0.22).cgColor
|
||||||
content.layer?.borderColor = border
|
content.layer?.borderColor = border
|
||||||
returnKey.layer?.borderColor = border
|
returnKey.layer?.borderColor = SunsetPalette.secondaryText.withAlphaComponent(0.35).cgColor
|
||||||
actionPill.layer?.borderColor = border
|
actionPill.layer?.borderColor = SunsetPalette.accent.withAlphaComponent(0.35).cgColor
|
||||||
tint.layer?.backgroundColor = NSColor.textBackgroundColor.withAlphaComponent(0.85).cgColor
|
actionPill.layer?.backgroundColor = SunsetPalette.accent.withAlphaComponent(0.08).cgColor
|
||||||
}
|
}
|
||||||
window.initialFirstResponder = searchField
|
window.initialFirstResponder = searchField
|
||||||
}
|
}
|
||||||
@@ -315,10 +319,11 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
|||||||
icon.setAccessibilityElement(false)
|
icon.setAccessibilityElement(false)
|
||||||
let label = NSTextField(labelWithString: "")
|
let label = NSTextField(labelWithString: "")
|
||||||
label.font = .systemFont(ofSize: 14, weight: .medium)
|
label.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
|
label.textColor = SunsetPalette.text
|
||||||
label.lineBreakMode = .byTruncatingTail
|
label.lineBreakMode = .byTruncatingTail
|
||||||
let kind = NSTextField(labelWithString: "Application")
|
let kind = NSTextField(labelWithString: "Application")
|
||||||
kind.font = .systemFont(ofSize: 14)
|
kind.font = .systemFont(ofSize: 14)
|
||||||
kind.textColor = .secondaryLabelColor
|
kind.textColor = SunsetPalette.secondaryText
|
||||||
kind.setContentCompressionResistancePriority(.required, for: .horizontal)
|
kind.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
kind.setAccessibilityElement(false)
|
kind.setAccessibilityElement(false)
|
||||||
cell.imageView = icon
|
cell.imageView = icon
|
||||||
@@ -418,13 +423,12 @@ private final class LauncherPanel: NSPanel {
|
|||||||
@MainActor
|
@MainActor
|
||||||
private final class LauncherRowView: NSTableRowView {
|
private final class LauncherRowView: NSTableRowView {
|
||||||
override func drawSelection(in dirtyRect: NSRect) {
|
override func drawSelection(in dirtyRect: NSRect) {
|
||||||
let selection = NSBezierPath(roundedRect: bounds, xRadius: 10, yRadius: 10)
|
let selection = NSBezierPath(roundedRect: bounds.insetBy(dx: 0.75, dy: 0.75), xRadius: 10, yRadius: 10)
|
||||||
NSColor.unemphasizedSelectedContentBackgroundColor.withAlphaComponent(0.55).setFill()
|
SunsetPalette.selectionFill.setFill()
|
||||||
selection.fill()
|
selection.fill()
|
||||||
if NSWorkspace.shared.accessibilityDisplayShouldIncreaseContrast {
|
let increasedContrast = NSWorkspace.shared.accessibilityDisplayShouldIncreaseContrast
|
||||||
NSColor.labelColor.setStroke()
|
(increasedContrast ? SunsetPalette.accent : SunsetPalette.selectionBorder).setStroke()
|
||||||
selection.lineWidth = 1
|
selection.lineWidth = increasedContrast ? 1.5 : 0.5
|
||||||
selection.stroke()
|
selection.stroke()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user