From 77c33e526bf5b089676d83efdf7fb24aa394bbc8 Mon Sep 17 00:00:00 2001 From: Qiu Yuzhou Date: Tue, 6 Oct 2026 00:52:35 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=A7=BB=E9=99=A4=E6=9C=89bug=E7=9A=84?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=BD=93=E5=89=8D=E9=80=89=E6=8B=A9=E7=9A=84?= =?UTF-8?q?=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 此实现企图通过捕获对列表空白区域的点击以触发移除列表的当前选中项 --- .../ServerTreeBlankClickObserver.swift | 79 ------------------- .../App/UI/Servers/ServersView.swift | 11 +-- 2 files changed, 1 insertion(+), 89 deletions(-) delete mode 100644 ShadowsocksX-NG2/App/UI/Servers/ServerTreeBlankClickObserver.swift diff --git a/ShadowsocksX-NG2/App/UI/Servers/ServerTreeBlankClickObserver.swift b/ShadowsocksX-NG2/App/UI/Servers/ServerTreeBlankClickObserver.swift deleted file mode 100644 index df6d7dc4..00000000 --- a/ShadowsocksX-NG2/App/UI/Servers/ServerTreeBlankClickObserver.swift +++ /dev/null @@ -1,79 +0,0 @@ -import SwiftUI - -/// List 没有空白点击回调;用原生表格的行命中判断,不拦截行选择、按钮或滚动条。 -struct ServerTreeBlankClickObserver: NSViewRepresentable { - let onBlankClick: () -> Void - - func makeNSView(context: Context) -> ObserverView { - let view = ObserverView() - view.onBlankClick = onBlankClick - return view - } - - func updateNSView(_ view: ObserverView, context: Context) { - view.onBlankClick = onBlankClick - } - - static func dismantleNSView(_ view: ObserverView, coordinator: ()) { - view.stopObserving() - } - - final class ObserverView: NSView { - var onBlankClick: (() -> Void)? - private var monitor: Any? - - override func hitTest(_ point: NSPoint) -> NSView? { nil } - - override func viewDidMoveToWindow() { - super.viewDidMoveToWindow() - stopObserving() - guard window != nil else { return } - monitor = NSEvent.addLocalMonitorForEvents(matching: .leftMouseDown) { [weak self] event in - self?.handleClick(event) - return event - } - } - - func stopObserving() { - if let monitor { NSEvent.removeMonitor(monitor) } - monitor = nil - } - - private func handleClick(_ event: NSEvent) { - guard let window, event.window === window, - visibleRect.contains(convert(event.locationInWindow, from: nil)) - else { return } - // 背景探针与 List 的原生滚动视图是兄弟;向上寻找共同容器, - // 只接受点击点处的表格,排除滚动条和其他列表。 - var container = superview - while let current = container { - if let table = table(at: event.locationInWindow, in: current) { - // 覆盖式滚动条可能落在表格几何范围内,实际命中必须属于表格。 - guard let content = window.contentView else { return } - let hitPoint = - content.superview?.convert(event.locationInWindow, from: nil) - ?? event.locationInWindow - var hit = content.hitTest(hitPoint) - while let view = hit, view !== table { hit = view.superview } - guard hit === table else { return } - let point = table.convert(event.locationInWindow, from: nil) - if table.row(at: point) == -1 { onBlankClick?() } - return - } - container = current.superview - } - } - - private func table(at point: NSPoint, in view: NSView) -> NSTableView? { - if let table = view as? NSTableView, - table.visibleRect.contains(table.convert(point, from: nil)) - { - return table - } - for child in view.subviews { - if let table = table(at: point, in: child) { return table } - } - return nil - } - } -} diff --git a/ShadowsocksX-NG2/App/UI/Servers/ServersView.swift b/ShadowsocksX-NG2/App/UI/Servers/ServersView.swift index 77ff9919..72994500 100644 --- a/ShadowsocksX-NG2/App/UI/Servers/ServersView.swift +++ b/ShadowsocksX-NG2/App/UI/Servers/ServersView.swift @@ -37,7 +37,6 @@ struct ServersView: View { @State private var deleteTarget: NodeID? @State private var moveTarget: NodeID? @State private var rootDropHovering = false - @FocusState private var treeFocused: Bool // 分享 popover 打开时冻结载荷、顶部资料与建议文件名。 @State var shareContext: ShareContext? @@ -105,19 +104,11 @@ struct ServersView: View { } } .listStyle(.sidebar) - .focusable(interactions: .edit) - .focused($treeFocused) .onKeyPress(.escape) { - guard treeFocused, selection != nil else { return .ignored } + guard selection != nil else { return .ignored } selection = nil return .handled } - .background { - ServerTreeBlankClickObserver { - selection = nil - treeFocused = true - } - } .overlay(alignment: .center) { if workflow.tree.isEmpty { ContentUnavailableView(