style: align actions panel with footer and tighten its layout
This commit is contained in:
@@ -79,7 +79,7 @@ Applications with identical display names show a muted containing-folder hint be
|
|||||||
|
|
||||||
Select an application and open **Actions ⌘K**. The red **Uninstall application** action opens a review sheet with file checkboxes, paths, sizes, and a selected-item count. Filtering by name or path does not change selections. Up/Down highlights rows; Space toggles the highlighted eligible item when the file list has focus. Back or Escape returns to the launcher. Return or the uninstall button opens a final confirmation; nothing is moved merely by choosing the action.
|
Select an application and open **Actions ⌘K**. The red **Uninstall application** action opens a review sheet with file checkboxes, paths, sizes, and a selected-item count. Filtering by name or path does not change selections. Up/Down highlights rows; Space toggles the highlighted eligible item when the file list has focus. Back or Escape returns to the launcher. Return or the uninstall button opens a final confirmation; nothing is moved merely by choosing the action.
|
||||||
|
|
||||||
The actions selector is an arrowless panel inside the launcher, aligned to the right edge of the footer action pill and opening directly above it. It shares the launcher's frosted Sunset background, rounded selection styling, and Return key badge. The application name appears at the top and borderless action search stays at the bottom below a divider. Escape, Command-K, an outside click, or switching away closes it. Closing while Conjure remains active restores launcher search focus; switching away does not steal focus back. Return in action search chooses the visible action rather than launching the underlying application.
|
The actions selector is an arrowless panel inside the launcher, aligned to the bottom and right edges of the footer action pill, replacing it visually while open rather than floating above it. It shares the launcher's frosted Sunset background, 38-point selection row height, 14-point action text, rounded selection styling, and Return key badge; selection width follows the panel. The application name appears at the top and borderless action search sits eight points from the bottom, without a divider. The transition is direct, without a morph animation. Escape, Command-K, an outside click, or switching away closes it and restores the pill. Closing while Conjure remains active restores launcher search focus; switching away does not steal focus back. Return in action search chooses the visible action rather than launching the underlying application.
|
||||||
|
|
||||||
Discovery includes the application bundle and exact bundle-ID paths in your Library: Preferences, Caches, Application Support, Logs, Saved Application State, Application Scripts, and Containers. Embedded extension identifiers are included. App-group entitlements are read from validated native code signatures; related shared Group Containers and Application Scripts remain unchecked because other applications may use them. Conjure does not guess name-based leftovers or include package receipts. This is conservative discovery, not a guarantee that every associated file is found.
|
Discovery includes the application bundle and exact bundle-ID paths in your Library: Preferences, Caches, Application Support, Logs, Saved Application State, Application Scripts, and Containers. Embedded extension identifiers are included. App-group entitlements are read from validated native code signatures; related shared Group Containers and Application Scripts remain unchecked because other applications may use them. Conjure does not guess name-based leftovers or include package receipts. This is conservative discovery, not a guarantee that every associated file is found.
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
private let empty = NSTextField(labelWithString: "No matching actions")
|
private let empty = NSTextField(labelWithString: "No matching actions")
|
||||||
private let applicationLabel = NSTextField(labelWithString: "")
|
private let applicationLabel = NSTextField(labelWithString: "")
|
||||||
private var mouseMonitor: Any?
|
private var mouseMonitor: Any?
|
||||||
|
private weak var coveredPill: NSView?
|
||||||
|
private var coveredPillOpacity: Float = 1
|
||||||
var uninstall: (() -> Void)?
|
var uninstall: (() -> Void)?
|
||||||
var closed: (() -> Void)?
|
var closed: (() -> Void)?
|
||||||
var isShown: Bool { viewIfLoaded?.superview != nil }
|
var isShown: Bool { viewIfLoaded?.superview != nil }
|
||||||
@@ -38,12 +40,13 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
row.isSelected = true
|
row.isSelected = true
|
||||||
action.isBordered = false
|
action.isBordered = false
|
||||||
action.alignment = .left
|
action.alignment = .left
|
||||||
action.font = .systemFont(ofSize: 16, weight: .medium)
|
action.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
action.contentTintColor = .systemRed
|
action.contentTintColor = .systemRed
|
||||||
action.attributedTitle = NSAttributedString(string: action.title, attributes: [
|
action.attributedTitle = NSAttributedString(string: action.title, attributes: [
|
||||||
.foregroundColor: NSColor.systemRed, .font: NSFont.systemFont(ofSize: 16, weight: .medium)
|
.foregroundColor: NSColor.systemRed, .font: NSFont.systemFont(ofSize: 14, weight: .medium)
|
||||||
])
|
])
|
||||||
action.image = NSImage(systemSymbolName: "trash", accessibilityDescription: nil)
|
action.image = NSImage(systemSymbolName: "trash", accessibilityDescription: nil)?
|
||||||
|
.withSymbolConfiguration(NSImage.SymbolConfiguration(pointSize: 18, weight: .regular))
|
||||||
action.imagePosition = .imageLeading
|
action.imagePosition = .imageLeading
|
||||||
action.target = self
|
action.target = self
|
||||||
action.action = #selector(chooseAction)
|
action.action = #selector(chooseAction)
|
||||||
@@ -75,11 +78,9 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
empty.textColor = SunsetPalette.secondaryText
|
empty.textColor = SunsetPalette.secondaryText
|
||||||
empty.font = .systemFont(ofSize: 14)
|
empty.font = .systemFont(ofSize: 14)
|
||||||
empty.isHidden = true
|
empty.isHidden = true
|
||||||
let divider = NSBox()
|
|
||||||
divider.boxType = .separator
|
|
||||||
content.translatesAutoresizingMaskIntoConstraints = false
|
content.translatesAutoresizingMaskIntoConstraints = false
|
||||||
view.addSubview(content)
|
view.addSubview(content)
|
||||||
for child in [background, applicationLabel, row, empty, divider, search] {
|
for child in [background, applicationLabel, row, empty, search] {
|
||||||
child.translatesAutoresizingMaskIntoConstraints = false
|
child.translatesAutoresizingMaskIntoConstraints = false
|
||||||
if child !== background { content.addSubview(child) }
|
if child !== background { content.addSubview(child) }
|
||||||
}
|
}
|
||||||
@@ -89,7 +90,7 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
}
|
}
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
view.widthAnchor.constraint(equalToConstant: 360),
|
view.widthAnchor.constraint(equalToConstant: 360),
|
||||||
view.heightAnchor.constraint(equalToConstant: 148),
|
view.heightAnchor.constraint(equalToConstant: 116),
|
||||||
content.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
content.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
content.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
content.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
content.topAnchor.constraint(equalTo: view.topAnchor),
|
content.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
@@ -104,23 +105,20 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
row.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 8),
|
row.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 8),
|
||||||
row.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -8),
|
row.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -8),
|
||||||
row.topAnchor.constraint(equalTo: applicationLabel.bottomAnchor, constant: 10),
|
row.topAnchor.constraint(equalTo: applicationLabel.bottomAnchor, constant: 10),
|
||||||
row.heightAnchor.constraint(equalToConstant: 44),
|
row.heightAnchor.constraint(equalToConstant: LauncherRowView.rowHeight),
|
||||||
action.leadingAnchor.constraint(equalTo: row.leadingAnchor, constant: 10),
|
action.leadingAnchor.constraint(equalTo: row.leadingAnchor, constant: 8),
|
||||||
action.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
action.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
||||||
action.trailingAnchor.constraint(equalTo: returnKey.leadingAnchor, constant: -12),
|
action.trailingAnchor.constraint(equalTo: returnKey.leadingAnchor, constant: -12),
|
||||||
returnKey.trailingAnchor.constraint(equalTo: row.trailingAnchor, constant: -12),
|
returnKey.trailingAnchor.constraint(equalTo: row.trailingAnchor, constant: -12),
|
||||||
returnKey.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
returnKey.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
||||||
returnKey.widthAnchor.constraint(equalToConstant: 20),
|
returnKey.widthAnchor.constraint(equalToConstant: 18),
|
||||||
returnKey.heightAnchor.constraint(equalToConstant: 20),
|
returnKey.heightAnchor.constraint(equalToConstant: 18),
|
||||||
empty.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 16),
|
empty.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 16),
|
||||||
empty.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
empty.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
||||||
divider.leadingAnchor.constraint(equalTo: content.leadingAnchor),
|
|
||||||
divider.trailingAnchor.constraint(equalTo: content.trailingAnchor),
|
|
||||||
divider.bottomAnchor.constraint(equalTo: search.topAnchor, constant: -10),
|
|
||||||
search.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 16),
|
search.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 16),
|
||||||
search.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -16),
|
search.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -16),
|
||||||
search.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -12),
|
search.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -8),
|
||||||
search.heightAnchor.constraint(equalToConstant: 28)
|
search.heightAnchor.constraint(equalToConstant: 24)
|
||||||
])
|
])
|
||||||
content.layer?.borderColor = SunsetPalette.secondaryText.withAlphaComponent(0.22).cgColor
|
content.layer?.borderColor = SunsetPalette.secondaryText.withAlphaComponent(0.22).cgColor
|
||||||
returnKey.layer?.borderColor = SunsetPalette.secondaryText.withAlphaComponent(0.35).cgColor
|
returnKey.layer?.borderColor = SunsetPalette.secondaryText.withAlphaComponent(0.35).cgColor
|
||||||
@@ -137,8 +135,12 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
host.addSubview(view)
|
host.addSubview(view)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
view.trailingAnchor.constraint(equalTo: pill.trailingAnchor),
|
view.trailingAnchor.constraint(equalTo: pill.trailingAnchor),
|
||||||
view.bottomAnchor.constraint(equalTo: pill.topAnchor, constant: -8)
|
view.bottomAnchor.constraint(equalTo: pill.bottomAnchor)
|
||||||
])
|
])
|
||||||
|
coveredPill = pill
|
||||||
|
coveredPillOpacity = pill.layer?.opacity ?? 1
|
||||||
|
pill.layer?.opacity = 0
|
||||||
|
pill.setAccessibilityHidden(true)
|
||||||
window.makeFirstResponder(search)
|
window.makeFirstResponder(search)
|
||||||
(search.currentEditor() as? NSTextView)?.insertionPointColor = SunsetPalette.accent
|
(search.currentEditor() as? NSTextView)?.insertionPointColor = SunsetPalette.accent
|
||||||
mouseMonitor = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { [weak self, weak pill] event in
|
mouseMonitor = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { [weak self, weak pill] event in
|
||||||
@@ -158,6 +160,9 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
guard isShown else { return }
|
guard isShown else { return }
|
||||||
if let mouseMonitor { NSEvent.removeMonitor(mouseMonitor) }
|
if let mouseMonitor { NSEvent.removeMonitor(mouseMonitor) }
|
||||||
mouseMonitor = nil
|
mouseMonitor = nil
|
||||||
|
coveredPill?.layer?.opacity = coveredPillOpacity
|
||||||
|
coveredPill?.setAccessibilityHidden(false)
|
||||||
|
coveredPill = nil
|
||||||
view.removeFromSuperview()
|
view.removeFromSuperview()
|
||||||
closed?()
|
closed?()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
|||||||
tableView.headerView = nil
|
tableView.headerView = nil
|
||||||
tableView.style = .plain
|
tableView.style = .plain
|
||||||
tableView.backgroundColor = .clear
|
tableView.backgroundColor = .clear
|
||||||
tableView.rowHeight = 38
|
tableView.rowHeight = LauncherRowView.rowHeight
|
||||||
tableView.intercellSpacing = .zero
|
tableView.intercellSpacing = .zero
|
||||||
tableView.columnAutoresizingStyle = .uniformColumnAutoresizingStyle
|
tableView.columnAutoresizingStyle = .uniformColumnAutoresizingStyle
|
||||||
tableView.allowsMultipleSelection = false
|
tableView.allowsMultipleSelection = false
|
||||||
@@ -603,6 +603,8 @@ private final class LauncherPanel: NSPanel {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
final class LauncherRowView: NSTableRowView {
|
final class LauncherRowView: NSTableRowView {
|
||||||
|
static let rowHeight: CGFloat = 38
|
||||||
|
|
||||||
override func drawSelection(in dirtyRect: NSRect) {
|
override func drawSelection(in dirtyRect: NSRect) {
|
||||||
let selection = NSBezierPath(roundedRect: bounds.insetBy(dx: 0.75, dy: 0.75), xRadius: 10, yRadius: 10)
|
let selection = NSBezierPath(roundedRect: bounds.insetBy(dx: 0.75, dy: 0.75), xRadius: 10, yRadius: 10)
|
||||||
SunsetPalette.selectionFill.setFill()
|
SunsetPalette.selectionFill.setFill()
|
||||||
|
|||||||
Reference in New Issue
Block a user