@@ -95,6 +95,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
9595 let saveURL = FileManager . default. homeDirectoryForCurrentUser
9696 . appendingPathComponent ( " .notty.txt " )
9797
98+ // Pinned mode: panel stays open even when clicking outside
99+ var isPinned : Bool = false
100+ var titleButton : NSButton !
101+
98102 var hotKeyRef : EventHotKeyRef ?
99103
100104 func applicationDidFinishLaunching( _ notification: Notification ) {
@@ -143,15 +147,26 @@ class AppDelegate: NSObject, NSApplicationDelegate {
143147 container. layer? . borderColor = NSColor ( white: 1.0 , alpha: 0.08 ) . cgColor
144148 container. autoresizingMask = [ . width, . height] // adapts when panel is resized
145149
146- // --- 4. Title "Notty" at the top ---
150+ // --- 4. Title "Notty" ---
147151 let title = NSTextField ( labelWithString: " Notty " )
148152 title. frame = NSRect ( x: 16 , y: 365 , width: 200 , height: 24 )
149153 title. font = NSFont ( name: " Space Mono " , size: 14 )
150154 ?? NSFont . monospacedSystemFont ( ofSize: 14 , weight: . bold)
151155 title. textColor = NSColor ( white: 1.0 , alpha: 0.5 )
152- title. autoresizingMask = [ . minYMargin] // stays pinned to the top
156+ title. autoresizingMask = [ . minYMargin]
153157 container. addSubview ( title)
154158
159+ // --- 4b. Pin button (top-right) ---
160+ titleButton = NSButton ( frame: NSRect ( x: 288 , y: 364 , width: 22 , height: 22 ) )
161+ titleButton. bezelStyle = . inline
162+ titleButton. isBordered = false
163+ titleButton. image = NSImage ( systemSymbolName: " pin " , accessibilityDescription: " Pin " )
164+ titleButton. contentTintColor = NSColor ( white: 1.0 , alpha: 0.2 )
165+ titleButton. autoresizingMask = [ . minXMargin, . minYMargin]
166+ titleButton. target = self
167+ titleButton. action = #selector( togglePinned)
168+ container. addSubview ( titleButton)
169+
155170 // --- 5. Text editor with scroll ---
156171 let scrollView = NSScrollView ( frame: NSRect ( x: 12 , y: 12 , width: 296 , height: 345 ) )
157172 scrollView. autoresizingMask = [ . width, . height] // adapts on resize
@@ -218,10 +233,20 @@ class AppDelegate: NSObject, NSApplicationDelegate {
218233 } , 1 , & eventType, Unmanaged . passUnretained ( self ) . toOpaque ( ) , nil )
219234 }
220235
236+ // --- Toggle pinned mode ---
237+ @objc func togglePinned( ) {
238+ isPinned. toggle ( )
239+ let icon = isPinned ? " pin.fill " : " pin "
240+ titleButton. image = NSImage ( systemSymbolName: icon, accessibilityDescription: " Pin " )
241+ titleButton. contentTintColor = isPinned
242+ ? NSColor ( white: 1.0 , alpha: 0.8 )
243+ : NSColor ( white: 1.0 , alpha: 0.2 )
244+ }
245+
221246 // --- Toggle: open or close ---
222247 @objc func togglePanel( ) {
223248 if panel. isVisible {
224- closePanel ( )
249+ closePanel ( force : true )
225250 } else {
226251 // If the panel was just closed by the global monitor (clicking the icon
227252 // counts as a global click), don't reopen it immediately
@@ -280,8 +305,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
280305 }
281306 }
282307
283- // --- Close the panel ---
284- func closePanel( ) {
308+ // --- Close the panel (force: bypasses pinned mode, used by icon toggle) ---
309+ func closePanel( force: Bool = false ) {
310+ if isPinned && !force { return }
285311 // Switch icon back to "folder" (closed)
286312 statusItem. button? . image = NSImage (
287313 systemSymbolName: " folder " ,
0 commit comments