Skip to content

Commit f0bb463

Browse files
committed
added more styles like for code, color
1 parent 3884a20 commit f0bb463

12 files changed

+876
-339
lines changed

ClipPocket.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@
401401
ENABLE_HARDENED_RUNTIME = YES;
402402
ENABLE_PREVIEWS = YES;
403403
GENERATE_INFOPLIST_FILE = YES;
404+
INFOPLIST_KEY_LSUIElement = YES;
404405
INFOPLIST_KEY_NSHumanReadableCopyright = "";
405406
LD_RUNPATH_SEARCH_PATHS = (
406407
"$(inherited)",
@@ -428,6 +429,7 @@
428429
ENABLE_HARDENED_RUNTIME = YES;
429430
ENABLE_PREVIEWS = YES;
430431
GENERATE_INFOPLIST_FILE = YES;
432+
INFOPLIST_KEY_LSUIElement = YES;
431433
INFOPLIST_KEY_NSHumanReadableCopyright = "";
432434
LD_RUNPATH_SEARCH_PATHS = (
433435
"$(inherited)",

ClipPocket/ClipPocketApp.swift

Lines changed: 375 additions & 203 deletions
Large diffs are not rendered by default.
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import SwiftUI
2+
import AppKit
3+
4+
struct ClipboardManagerView: View {
5+
@EnvironmentObject var appDelegate: AppDelegate
6+
@State private var hoveredItemId: UUID?
7+
@State private var searchText: String = ""
8+
9+
var filteredItems: [ClipboardItem] {
10+
if searchText.isEmpty {
11+
return appDelegate.clipboardItems
12+
} else {
13+
return appDelegate.clipboardItems.filter { item in
14+
switch item.type {
15+
case .text, .code:
16+
return (item.content as? String)?.localizedCaseInsensitiveContains(searchText) ?? false
17+
case .image:
18+
// Images can't be searched by text, but we could potentially search by file name or dimensions
19+
return false
20+
case .color:
21+
if let colorString = item.content as? String {
22+
// Search by color hex value
23+
return colorString.localizedCaseInsensitiveContains(searchText)
24+
}
25+
return false
26+
}
27+
}
28+
}
29+
}
30+
31+
var body: some View {
32+
ZStack {
33+
RoundedRectangle(cornerRadius: 20)
34+
.fill(Color.clear)
35+
.background(
36+
VisualEffectView(material: .hudWindow, blendingMode: .behindWindow)
37+
.mask(RoundedRectangle(cornerRadius: 20))
38+
)
39+
40+
VStack(spacing: 0) {
41+
HStack {
42+
Text("Clipboard History")
43+
.font(.headline)
44+
.foregroundColor(.primary)
45+
Spacer()
46+
Button(action: {
47+
NSApp.sendAction(#selector(AppDelegate.openSettings), to: nil, from: nil)
48+
}) {
49+
Image(systemName: "gearshape")
50+
.foregroundColor(.gray)
51+
}
52+
.buttonStyle(PlainButtonStyle())
53+
.help("Open Settings")
54+
55+
Button(action: {
56+
NSApp.sendAction(#selector(AppDelegate.hideClipboardManager), to: nil, from: nil)
57+
}) {
58+
Image(systemName: "xmark.circle.fill")
59+
.foregroundColor(.gray)
60+
}
61+
.buttonStyle(PlainButtonStyle())
62+
.help("Close")
63+
}
64+
.padding()
65+
66+
// Modern Search Bar
67+
ZStack(alignment: .leading) {
68+
RoundedRectangle(cornerRadius: 8)
69+
.fill(Color(NSColor.controlBackgroundColor))
70+
.frame(height: 36)
71+
.shadow(color: Color.black.opacity(0.1), radius: 2, x: 0, y: 1)
72+
73+
HStack {
74+
Image(systemName: "magnifyingglass")
75+
.foregroundColor(.gray)
76+
.padding(.leading, 8)
77+
78+
TextField("Search clipboard items", text: $searchText)
79+
.textFieldStyle(PlainTextFieldStyle())
80+
.padding(.vertical, 8)
81+
}
82+
}
83+
.padding(.horizontal)
84+
.padding(.bottom, 10)
85+
86+
ScrollView(.horizontal, showsIndicators: false) {
87+
HStack(spacing: 10) {
88+
ForEach(filteredItems) { item in
89+
ClipboardItemView(item: item)
90+
.frame(width: 180, height: 100)
91+
.background(Color(NSColor.windowBackgroundColor).opacity(0.5))
92+
.cornerRadius(8)
93+
.shadow(radius: 2)
94+
.onHover { isHovered in
95+
hoveredItemId = isHovered ? item.id : nil
96+
}
97+
.scaleEffect(hoveredItemId == item.id ? 1.05 : 1.0)
98+
.animation(.easeInOut(duration: 0.2), value: hoveredItemId)
99+
.onTapGesture {
100+
appDelegate.copyItemToClipboard(item)
101+
NSApp.sendAction(#selector(AppDelegate.hideClipboardManager), to: nil, from: nil)
102+
}
103+
}
104+
}
105+
.padding(.horizontal)
106+
}
107+
.frame(height: 120)
108+
}
109+
.padding(.vertical)
110+
}
111+
.frame(width: NSScreen.main?.visibleFrame.width ?? 600, height: 230)
112+
.onAppear {
113+
NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
114+
if event.keyCode == 53 { // 53 is the key code for Esc
115+
NSApp.sendAction(#selector(AppDelegate.hideClipboardManager), to: nil, from: nil)
116+
return nil
117+
}
118+
return event
119+
}
120+
}
121+
}
122+
}
123+
124+
struct VisualEffectView: NSViewRepresentable {
125+
let material: NSVisualEffectView.Material
126+
let blendingMode: NSVisualEffectView.BlendingMode
127+
128+
func makeNSView(context: Context) -> NSVisualEffectView {
129+
let visualEffectView = NSVisualEffectView()
130+
visualEffectView.material = material
131+
visualEffectView.blendingMode = blendingMode
132+
visualEffectView.state = .active
133+
return visualEffectView
134+
}
135+
136+
func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context) {
137+
visualEffectView.material = material
138+
visualEffectView.blendingMode = blendingMode
139+
}
140+
}

ClipPocket/ClipboardMenuView.swift

Lines changed: 181 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ struct ClipboardMenuView: View {
1313
ClipboardItemView(item: item)
1414
.frame(width: 180, height: 100)
1515
.background(Color(NSColor.controlBackgroundColor))
16-
.cornerRadius(8)
16+
.cornerRadius(30)
1717
.shadow(radius: 2)
1818
.onHover { isHovered in
1919
hoveredItemId = isHovered ? item.id : nil
@@ -39,18 +39,190 @@ struct ClipboardItemView: View {
3939

4040
var body: some View {
4141
VStack {
42-
if item.type == .text {
43-
Text(item.displayString)
44-
.lineLimit(3)
45-
.font(.system(size: 12))
46-
} else if item.type == .image,
47-
let imageData = item.content as? Data,
48-
let nsImage = NSImage(data: imageData) {
42+
switch item.type {
43+
case .text:
44+
textView
45+
case .image:
46+
imageView
47+
case .color:
48+
colorView
49+
case .code:
50+
codeView
51+
}
52+
}
53+
.frame(width: 180, height: 100)
54+
.background(backgroundForType)
55+
.cornerRadius(10)
56+
.overlay(
57+
RoundedRectangle(cornerRadius: 10)
58+
.stroke(Color.gray.opacity(0.3), lineWidth: 1)
59+
)
60+
}
61+
62+
private var textView: some View {
63+
Text(item.displayString)
64+
.font(.system(size: 12))
65+
.padding(5)
66+
.lineLimit(4)
67+
}
68+
69+
private var imageView: some View {
70+
Group {
71+
if let imageData = item.content as? Data,
72+
let nsImage = NSImage(data: imageData) {
4973
Image(nsImage: nsImage)
5074
.resizable()
5175
.aspectRatio(contentMode: .fit)
76+
.frame(width: 170, height: 90)
77+
} else {
78+
Text("Invalid Image")
79+
.font(.caption)
80+
.foregroundColor(.red)
81+
}
82+
}
83+
}
84+
85+
private var colorView: some View {
86+
Group {
87+
if let colorString = item.content as? String,
88+
let color = Color(hex: colorString) {
89+
let textColor = color.contrastingTextColor(_color: item.content as! String) // Choose light or dark text based on background color
90+
91+
VStack {
92+
Rectangle()
93+
.fill(color)
94+
.frame(width: 170, height: 70)
95+
Text(colorString)
96+
.font(.caption)
97+
.foregroundColor(textColor)
98+
.multilineTextAlignment(.center) // Center the text
99+
}
100+
} else {
101+
Text("Invalid Color")
102+
.font(.caption)
103+
.foregroundColor(.red)
104+
.multilineTextAlignment(.center)
105+
}
106+
}
107+
.frame(width: 180, height: 100)
108+
}
109+
110+
private var codeView: some View {
111+
VStack(alignment: .leading, spacing: 0) {
112+
HStack {
113+
Circle()
114+
.fill(Color.red)
115+
.frame(width: 10, height: 10)
116+
Circle()
117+
.fill(Color.yellow)
118+
.frame(width: 10, height: 10)
119+
Circle()
120+
.fill(Color.green)
121+
.frame(width: 10, height: 10)
122+
Spacer()
123+
Text("Code Snippet")
124+
.font(.system(size: 10))
125+
.foregroundColor(.secondary)
126+
}
127+
.padding(.horizontal, 8)
128+
.padding(.vertical, 4)
129+
.background(Color(hex: "#2b2b2b"))
130+
131+
ScrollView {
132+
Text(applySyntaxHighlighting(to: item.displayString))
133+
.font(.system(size: 10, design: .monospaced))
134+
.padding(8)
135+
.frame(maxWidth: .infinity, alignment: .leading)
136+
}
137+
}
138+
.background(Color(hex: "#1e1e1e"))
139+
.cornerRadius(8)
140+
}
141+
142+
private func applySyntaxHighlighting(to code: String) -> AttributedString {
143+
var attributedString = AttributedString(code)
144+
145+
let keywords = ["func", "var", "let", "if", "else", "for", "while", "return", "class", "struct", "enum"]
146+
let types = ["String", "Int", "Double", "Bool", "Array", "Dictionary"]
147+
148+
for keyword in keywords {
149+
if let range = attributedString.range(of: keyword) {
150+
attributedString[range].foregroundColor = .purple
151+
attributedString[range].font = .system(size: 10, weight: .bold, design: .monospaced)
152+
}
153+
}
154+
155+
for type in types {
156+
if let range = attributedString.range(of: type) {
157+
attributedString[range].foregroundColor = .blue
158+
}
159+
}
160+
161+
// Highlight string literals
162+
let stringPattern = "\"[^\"]*\""
163+
if let regex = try? NSRegularExpression(pattern: stringPattern) {
164+
let nsRange = NSRange(code.startIndex..., in: code)
165+
for match in regex.matches(in: code, options: [], range: nsRange) {
166+
if let range = Range(match.range, in: code) {
167+
let matchedString = String(code[range])
168+
if let attributedRange = attributedString.range(of: matchedString) {
169+
attributedString[attributedRange].foregroundColor = .green
170+
}
171+
}
172+
}
173+
}
174+
175+
return attributedString
176+
}
177+
178+
private var backgroundForType: some View {
179+
Group {
180+
switch item.type {
181+
case .text:
182+
Color(NSColor.textBackgroundColor)
183+
case .image:
184+
Color(NSColor.windowBackgroundColor)
185+
case .color:
186+
Color(NSColor.windowBackgroundColor)
187+
case .code:
188+
Color(hex: "#1e1e1e") // Dark background for code
189+
}
52190
}
53191
}
54-
.padding(5)
192+
}
193+
194+
extension Color {
195+
init?(hex: String) {
196+
var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
197+
hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
198+
199+
var rgb: UInt64 = 0
200+
201+
guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil }
202+
203+
self.init(
204+
red: Double((rgb & 0xFF0000) >> 16) / 255.0,
205+
green: Double((rgb & 0x00FF00) >> 8) / 255.0,
206+
blue: Double(rgb & 0x0000FF) / 255.0
207+
)
55208
}
209+
func luminance(_color: String) -> Double {
210+
var red: CGFloat = 0
211+
var green: CGFloat = 0
212+
var blue: CGFloat = 0
213+
214+
if let cgColor = Color.init(hex: _color)?.cgColor?.components {
215+
red = cgColor[0]
216+
green = cgColor[1]
217+
blue = cgColor[2]
218+
}
219+
220+
// Luminance formula for sRGB colors
221+
return 0.299 * Double(red) + 0.587 * Double(green) + 0.114 * Double(blue)
222+
}
223+
224+
// Choose light or dark based on the luminance
225+
func contrastingTextColor(_color: String) -> Color {
226+
return self.luminance(_color: _color) > 0.5 ? .black : .white
227+
}
56228
}

0 commit comments

Comments
 (0)