Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion Sources/Pesty/AppController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ final class AppController: NSObject, NSApplicationDelegate {
store.source = requestedSource ?? .history
store.applyHistoryPolicy()
store.prepareForBarPresentation()
store.inlinePreviewVisible = false

if barController == nil {
barController = BarWindowController()
Expand All @@ -200,9 +201,25 @@ final class AppController: NSObject, NSApplicationDelegate {

func hideBar() {
stopKeyMonitor()
store.inlinePreviewVisible = false
barController?.setInlinePreviewVisible(false)
barController?.hide()
}

func toggleInlinePreview() {
guard Settings.shared.clipPreviewStyle == .inlinePesty,
store.source != .pasteStack,
store.selectedItem != nil else { return }
store.inlinePreviewVisible.toggle()
barController?.setInlinePreviewVisible(store.inlinePreviewVisible)
}

func hideInlinePreview() {
guard store.inlinePreviewVisible else { return }
store.inlinePreviewVisible = false
barController?.setInlinePreviewVisible(false)
}

func resizeVisibleBar(to height: Double) {
barController?.resize(to: CGFloat(height))
}
Expand Down Expand Up @@ -457,7 +474,11 @@ final class AppController: NSObject, NSApplicationDelegate {

switch code {
case kVK_Space:
if store.source == .pasteStack {
if Settings.shared.clipPreviewStyle == .inlinePesty,
store.source != .pasteStack,
store.selectedItem != nil {
toggleInlinePreview()
} else if store.source == .pasteStack {
QuickLookService.shared.toggle(items: pasteSequence.displayEntries.map(\.item),
selectedID: pasteSequence.selectedEntry?.item.id)
} else {
Expand Down
32 changes: 32 additions & 0 deletions Sources/Pesty/Settings/Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@ import AppKit
import Carbon.HIToolbox
import Observation

enum ClipPreviewStyle: Int, CaseIterable, Identifiable {
case nativeQuickLook
case inlinePesty

var id: Int { rawValue }

var title: String {
switch self {
case .nativeQuickLook: "Native Quick Look"
case .inlinePesty: "Inline Pesty preview"
}
}

var detail: String {
switch self {
case .nativeQuickLook: "Open a macOS Quick Look panel with Space."
case .inlinePesty: "Show a rich preview with link titles and favicons inside Pesty."
}
}
}

enum ShortcutModifier: CaseIterable, Identifiable {
case command, option, control, shift
var id: Int { carbonValue }
Expand Down Expand Up @@ -154,6 +175,7 @@ final class Settings {
static let ignoredSourceAppBundleIDs = "ignoredSourceAppBundleIDs"
static let barHeight = "barHeight"
static let showBarResizeHandle = "showBarResizeHandle"
static let clipPreviewStyle = "clipPreviewStyle"
static let showMenuBarIcon = "showMenuBarIcon"
static let onboarded = "onboarded"
static let iCloudSync = "iCloudSync"
Expand Down Expand Up @@ -267,6 +289,14 @@ final class Settings {
didSet { guard isLoaded else { return }; d.set(showBarResizeHandle, forKey: Keys.showBarResizeHandle) }
}

var clipPreviewStyle: ClipPreviewStyle {
didSet {
guard isLoaded else { return }
d.set(clipPreviewStyle.rawValue, forKey: Keys.clipPreviewStyle)
if clipPreviewStyle != .inlinePesty { ClipboardStore.shared.inlinePreviewVisible = false }
}
}

var showMenuBarIcon: Bool {
didSet {
guard isLoaded else { return }
Expand Down Expand Up @@ -305,6 +335,7 @@ final class Settings {
Keys.ignoredSourceAppBundleIDs: [],
Keys.barHeight: 430.0,
Keys.showBarResizeHandle: false,
Keys.clipPreviewStyle: ClipPreviewStyle.nativeQuickLook.rawValue,
Keys.showMenuBarIcon: true,
Keys.onboarded: false,
Keys.iCloudSync: false
Expand All @@ -330,6 +361,7 @@ final class Settings {
.filter { !$0.isEmpty }
barHeight = d.double(forKey: Keys.barHeight)
showBarResizeHandle = d.bool(forKey: Keys.showBarResizeHandle)
clipPreviewStyle = ClipPreviewStyle(rawValue: d.integer(forKey: Keys.clipPreviewStyle)) ?? .nativeQuickLook
showMenuBarIcon = d.bool(forKey: Keys.showMenuBarIcon)
onboarded = d.bool(forKey: Keys.onboarded)
iCloudSync = d.bool(forKey: Keys.iCloudSync)
Expand Down
23 changes: 23 additions & 0 deletions Sources/Pesty/Settings/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,29 @@ private struct GeneralSettings: View {
}
}

settingsGroup("Clip Previews") {
settingCard {
VStack(alignment: .leading, spacing: 8) {
LabeledContent("Preview style") {
Picker("", selection: $settings.clipPreviewStyle) {
ForEach(ClipPreviewStyle.allCases) { style in
Text(style.title).tag(style)
}
}
.labelsHidden()
.pickerStyle(.menu)
}
.font(.system(size: 14))
.padding(.vertical, 10)
Divider()
Text(settings.clipPreviewStyle.detail)
.font(.caption)
.foregroundStyle(.secondary)
.padding(.bottom, 10)
}
}
}

#if !MAS
settingsGroup("Accessibility") {
settingCard {
Expand Down
1 change: 1 addition & 0 deletions Sources/Pesty/Store/ClipboardStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ final class ClipboardStore {
var source: BarSource = .history
var searchText: String = ""
var selectedID: UUID?
var inlinePreviewVisible = false
/// Used by the strip to restore its opening position without animating from
/// whichever card was selected the last time the bar was visible.
var initialScrollTargetID: UUID?
Expand Down
50 changes: 37 additions & 13 deletions Sources/Pesty/UI/BarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,39 @@ struct BarView: View {
private var showsStackDeck: Bool {
store.source == .history && store.searchText.isEmpty && sequence.hasSavedStacks
}
@State private var previewVisible = false
@State private var resizeStartHeight: Double?
@State private var cardFrames: [UUID: CGRect] = [:]

var body: some View {
ZStack {
panelBackground
}
.overlay(alignment: .top) {
VStack(spacing: 0) {
if settings.showBarResizeHandle { resizeHandle }
topBar
if store.source == .pasteStack {
PasteStackContentView()
} else {
HStack(spacing: 0) {
if previewVisible, let item = store.selectedItem {
SelectedClipPreviewView(item: item)
Divider()
}
if settings.clipPreviewStyle == .inlinePesty,
store.inlinePreviewVisible {
Spacer(minLength: 0)
strip.frame(height: 280)
} else {
strip
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
if settings.clipPreviewStyle == .inlinePesty,
store.source != .pasteStack,
store.inlinePreviewVisible,
let item = store.selectedItem {
PestyPreviewPopover(
item: item,
pointer: cardFrames[item.id].map { CGPoint(x: $0.midX, y: $0.midY) } ?? .zero)
}
}
.coordinateSpace(name: "PestyBar")
.onPreferenceChange(ClipCardFramePreferenceKey.self) { cardFrames = $0 }
.clipShape(RoundedCorners(radius: Theme.cornerRadius, corners: [.topLeft, .topRight]))
.ignoresSafeArea()
}
Expand All @@ -58,7 +67,7 @@ struct BarView: View {
.layoutPriority(1)
Spacer(minLength: 8)
if store.source != .pasteStack {
previewButton
if settings.clipPreviewStyle == .inlinePesty { previewButton }
startPasteStackButton
}
moreMenu
Expand All @@ -68,14 +77,14 @@ struct BarView: View {
}

private var previewButton: some View {
Button { previewVisible.toggle() } label: {
Image(systemName: previewVisible ? "rectangle.on.rectangle" : "rectangle.on.rectangle.angled")
Button { AppController.shared.toggleInlinePreview() } label: {
Image(systemName: store.inlinePreviewVisible ? "rectangle.on.rectangle" : "rectangle.on.rectangle.angled")
.font(.system(size: 14, weight: .semibold))
.foregroundStyle(previewVisible ? Theme.selection : Theme.textSecondary)
.foregroundStyle(store.inlinePreviewVisible ? Theme.selection : Theme.textSecondary)
.frame(width: 30, height: 30)
}
.buttonStyle(.plain)
.help(previewVisible ? "Hide clip preview" : "Show clip preview")
.help(store.inlinePreviewVisible ? "Hide clip preview" : "Show clip preview")
}

private var startPasteStackButton: some View {
Expand Down Expand Up @@ -201,6 +210,13 @@ struct BarView: View {
index: index,
selected: item.id == store.selectedID)
.id(item.id)
.background {
GeometryReader { proxy in
Color.clear.preference(
key: ClipCardFramePreferenceKey.self,
value: [item.id: proxy.frame(in: .named("PestyBar"))])
}
}
.transition(.asymmetric(
insertion: .scale(scale: 0.92).combined(with: .opacity),
removal: .opacity))
Expand Down Expand Up @@ -250,6 +266,14 @@ struct BarView: View {
}
}

private struct ClipCardFramePreferenceKey: PreferenceKey {
static var defaultValue: [UUID: CGRect] = [:]

static func reduce(value: inout [UUID: CGRect], nextValue: () -> [UUID: CGRect]) {
value.merge(nextValue(), uniquingKeysWith: { _, next in next })
}
}

struct RoundedCorners: Shape {
var radius: CGFloat
var corners: RectCorner
Expand Down
5 changes: 5 additions & 0 deletions Sources/Pesty/UI/BarWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ final class BarWindowController: NSWindowController, NSWindowDelegate {
panel.setFrame(frame, display: true)
}

func setInlinePreviewVisible(_ visible: Bool) {
let height = visible ? max(CGFloat(Settings.shared.barHeight), 620) : CGFloat(Settings.shared.barHeight)
resize(to: height)
}

func windowDidResignKey(_ notification: Notification) {
guard Settings.shared.hideOnClickOutside,
!isPresenting,
Expand Down
2 changes: 1 addition & 1 deletion Sources/Pesty/UI/ClipCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ struct ClipCardView: View {
case .file:
fileContent
case .link:
LinkPreviewContent(text: item.text ?? item.displayTitle, compact: true)
LinkCardPreview(text: item.text ?? item.displayTitle)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
case .richText:
RichTextContent(rtfData: item.rtfData, fallback: item.text ?? "", lineLimit: 10)
Expand Down
Loading