Skip to content

Commit

Permalink
support pinning a chat window
Browse files Browse the repository at this point in the history
  • Loading branch information
sakeven committed Aug 14, 2024
1 parent d5eba8d commit ad30013
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 9 deletions.
16 changes: 14 additions & 2 deletions Selected.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
3831FD332BA532D400A4262B /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = 3831FD322BA532D400A4262B /* Yams */; };
3831FD362BA6DE3D00A4262B /* UserConfigs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3831FD352BA6DE3D00A4262B /* UserConfigs.swift */; };
384A03732BC4173B00916303 /* ClipHistory.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 384A03712BC4173B00916303 /* ClipHistory.xcdatamodeld */; };
384EEF312C6CDAA300EF6412 /* ChatWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 384EEF302C6CDAA300EF6412 /* ChatWindow.swift */; };
385EA9E52C5FBDD8009B8F0E /* SpotlightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385EA9E42C5FBDD8009B8F0E /* SpotlightView.swift */; };
385EA9E72C5FC0D8009B8F0E /* Spotlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385EA9E62C5FC0D8009B8F0E /* Spotlight.swift */; };
385EA9EA2C5FC624009B8F0E /* HotKey in Frameworks */ = {isa = PBXBuildFile; productRef = 385EA9E92C5FC624009B8F0E /* HotKey */; };
Expand Down Expand Up @@ -124,6 +125,7 @@
3831FD342BA5C88300A4262B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
3831FD352BA6DE3D00A4262B /* UserConfigs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConfigs.swift; sourceTree = "<group>"; };
384A03722BC4173B00916303 /* ClipHistory.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ClipHistory.xcdatamodel; sourceTree = "<group>"; };
384EEF302C6CDAA300EF6412 /* ChatWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatWindow.swift; sourceTree = "<group>"; };
385EA9E42C5FBDD8009B8F0E /* SpotlightView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotlightView.swift; sourceTree = "<group>"; };
385EA9E62C5FC0D8009B8F0E /* Spotlight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Spotlight.swift; sourceTree = "<group>"; };
385EA9EE2C6256E2009B8F0E /* Updater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Updater.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -247,6 +249,14 @@
path = Plugin;
sourceTree = "<group>";
};
384EEF2F2C6CDA8E00EF6412 /* Windows */ = {
isa = PBXGroup;
children = (
384EEF302C6CDAA300EF6412 /* ChatWindow.swift */,
);
path = Windows;
sourceTree = "<group>";
};
386010562C2F9FEA00EB9AE7 /* ChatView */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -283,6 +293,7 @@
386AD7162B8F6BFA008C346D /* Selected */ = {
isa = PBXGroup;
children = (
384EEF2F2C6CDA8E00EF6412 /* Windows */,
388DABB52BD697F9000B2739 /* Fonts */,
3888D8082BA884DB00A7B75D /* Info.plist */,
38230C9D2B9F3307002A52E9 /* Plugin */,
Expand Down Expand Up @@ -627,6 +638,7 @@
38CC18B62B9C7B7C0023DF18 /* PopBarView.swift in Sources */,
38DEBE322B9D67F800CD2A35 /* OpenAI.swift in Sources */,
38DF1D522BC2408A0063A879 /* ClipView.swift in Sources */,
384EEF312C6CDAA300EF6412 /* ChatWindow.swift in Sources */,
3831FD362BA6DE3D00A4262B /* UserConfigs.swift in Sources */,
388DABAF2BD147F6000B2739 /* TextView.swift in Sources */,
38DF1D4A2BB7A3BC0063A879 /* Option.swift in Sources */,
Expand Down Expand Up @@ -799,7 +811,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 22;
CURRENT_PROJECT_VERSION = 23;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "Selected/Preview\\ Content";
DEVELOPMENT_TEAM = K38MUDUK3K;
Expand Down Expand Up @@ -832,7 +844,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 22;
CURRENT_PROJECT_VERSION = 23;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "Selected/Preview\\ Content";
DEVELOPMENT_TEAM = K38MUDUK3K;
Expand Down
4 changes: 3 additions & 1 deletion Selected/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
@objc func spaceDidChange() {
// 当空间改变时触发
ClipWindowManager.shared.forceCloseWindow()
ChatWindowManager.shared.closeAllWindows(.force)
SpotlightWindowManager.shared.forceCloseWindow()
}

Expand Down Expand Up @@ -175,9 +176,10 @@ func monitorMouseMove() {
}

if !updatedSelectedText &&
getBundleID() != SelfBundleID {
getBundleID() != SelfBundleID {
lastSelectedText = ""
_ = WindowManager.shared.closeAllWindows(.original)
ChatWindowManager.shared.closeAllWindows(.original)
}
}
}
Expand Down
20 changes: 20 additions & 0 deletions Selected/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,16 @@
}
}
},
"pin" : {
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "固定"
}
}
}
},
"plain text" : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -726,6 +736,16 @@
}
}
},
"unpin" : {
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "取消固定"
}
}
}
},
"URL:" : {
"localizations" : {
"zh-Hans" : {
Expand Down
2 changes: 1 addition & 1 deletion Selected/Plugin/GPTAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class GptAction: Decodable{
return PerformAction(
actionMeta: generic, complete: { ctx in
let chatCtx = ChatContext(text: ctx.Text, webPageURL: ctx.WebPageURL, bundleID: ctx.BundleID)
WindowManager.shared.createChatWindow(chatService: chatService, withContext: chatCtx)
ChatWindowManager.shared.createChatWindow(chatService: chatService, withContext: chatCtx)
})
}
}
Expand Down
12 changes: 12 additions & 0 deletions Selected/View/ChatView/ChatTextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import Defaults

struct ChatTextView: View {
let ctx: ChatContext

@ObservedObject var viewModel: MessageViewModel
@EnvironmentObject var pinned: PinnedModel
@State private var task: Task<Void, Never>? = nil

var body: some View {
Expand All @@ -23,6 +25,16 @@ struct ChatTextView: View {
HStack {
getIcon(ctx.bundleID)
Text(getAppName(ctx.bundleID))
Spacer()
Button {
pinned.pinned = !pinned.pinned
} label: {
if pinned.pinned {
Text("unpin")
} else {
Text("pin")
}
}
}.padding(.bottom, 10)
}
Text(ctx.text.trimmingCharacters(in: .whitespacesAndNewlines)).font(.custom( "UbuntuMonoNFM", size: 14)).foregroundColor(.gray).lineLimit(1)
Expand Down
5 changes: 0 additions & 5 deletions Selected/WindowManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,6 @@ class WindowManager {
}


func createChatWindow(chatService: AIChatService, withContext ctx: ChatContext) {
let contentView = ChatTextView(ctx: ctx, viewModel: MessageViewModel(chatService: chatService))
createWindow(rootView: AnyView(contentView), windType: .Alpha)
}

func closeOnlyPopbarWindows(_ mode: CloseWindowMode) -> Bool {
guard let windowCtr = windowCtr else {
return false
Expand Down
134 changes: 134 additions & 0 deletions Selected/Windows/ChatWindow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//
// ChatWindow.swift
// Selected
//
// Created by sake on 2024/8/14.
//

import Foundation
import SwiftUI


class ChatWindowManager {
static let shared = ChatWindowManager()

private var lock = NSLock()
private var windowCtrs = [ChatWindowController]()

func closeAllWindows(_ mode: CloseWindowMode) {
lock.lock()
defer {lock.unlock()}

for index in (0..<windowCtrs.count).reversed() {
if closeWindow(mode, windowCtr: windowCtrs[index]) {
windowCtrs.remove(at: index)
}
}
}

func createChatWindow(chatService: AIChatService, withContext ctx: ChatContext) {
let windowController = ChatWindowController(chatService: chatService, withContext: ctx)
closeAllWindows(.force)

lock.lock()
windowCtrs.append(windowController)
lock.unlock()

windowController.showWindow(nil)
// 如果你需要处理窗口关闭事件,你可以添加一个通知观察者
NotificationCenter.default.addObserver(forName: NSWindow.willCloseNotification, object: windowController.window, queue: nil) { _ in
}
}

private func closeWindow(_ mode: CloseWindowMode, windowCtr: ChatWindowController) -> Bool {
if windowCtr.pinnedModel.pinned {
return false
}

switch mode {
case .expanded:
let frame = windowCtr.window!.frame
let expandedFrame = NSRect(x: frame.origin.x - kExpandedLength,
y: frame.origin.y - kExpandedLength,
width: frame.size.width + kExpandedLength * 2,
height: frame.size.height + kExpandedLength * 2)
if !expandedFrame.contains(NSEvent.mouseLocation){
windowCtr.close()
return true
}

case .original:
let frame = windowCtr.window!.frame
if !frame.contains(NSEvent.mouseLocation){
windowCtr.close()
return true
}

case .force:
windowCtr.close()
return true
}
return false
}

}

private class ChatWindowController: NSWindowController, NSWindowDelegate {
var resultWindow: Bool
var onClose: (()->Void)?

var pinnedModel: PinnedModel

init(chatService: AIChatService, withContext ctx: ChatContext) {
var window: NSWindow
// 必须用 NSPanel 并设置 .nonactivatingPanel 以及 level 为 .screenSaver
// 保证悬浮在全屏应用之上
window = FloatingPanel(
contentRect: .zero,
backing: .buffered,
defer: false,
key: true
)

window.alphaValue = 0.9
self.resultWindow = true
pinnedModel = PinnedModel()

super.init(window: window)

let view = ChatTextView(ctx: ctx, viewModel: MessageViewModel(chatService: chatService)).environmentObject(pinnedModel)

window.center()
window.level = .screenSaver
window.contentView = NSHostingView(rootView: AnyView(view))
window.delegate = self // 设置代理为自己来监听窗口事件

let mouseLocation = NSEvent.mouseLocation // 获取鼠标当前位置
guard let screen = NSScreen.screens.first(where: { NSMouseInRect(mouseLocation, $0.frame, false) }) else {
return
}
let screenFrame = screen.visibleFrame // 获取目标屏幕的可见区域
let windowFrame = window.frame
// 确保窗口不会超出屏幕边缘
let x = (screenFrame.width - windowFrame.width) / 2 + screenFrame.origin.x
let y = (screenFrame.height - windowFrame.height)*3 / 4 + screenFrame.origin.y
window.setFrameOrigin(NSPoint(x: x, y: y))
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func windowDidResignActive(_ notification: Notification) {
self.close() // 如果需要的话
}

override func showWindow(_ sender: Any?) {
super.showWindow(sender)
}
}


class PinnedModel: ObservableObject {
@Published var pinned: Bool = false
}

0 comments on commit ad30013

Please sign in to comment.