Hide help menu popover arrow
Replace the SwiftUI .popover with an NSPopover presented via NSViewRepresentable that uses the shouldHideAnchor KVC trick to hide the arrow. The positioning rect is shifted toward the preferred edge to compensate for the hidden arrow's space. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c628174c1b
commit
ffa74d641c
1 changed files with 74 additions and 2 deletions
|
|
@ -7492,9 +7492,12 @@ private struct SidebarHelpMenuButton: View {
|
|||
}
|
||||
.buttonStyle(SidebarFooterIconButtonStyle())
|
||||
.frame(width: buttonSize, height: buttonSize, alignment: .center)
|
||||
.popover(isPresented: $isPopoverPresented, arrowEdge: .bottom) {
|
||||
.background(ArrowlessPopoverAnchor(
|
||||
isPresented: $isPopoverPresented,
|
||||
preferredEdge: .maxY
|
||||
) {
|
||||
helpPopover
|
||||
}
|
||||
})
|
||||
.accessibilityElement(children: .ignore)
|
||||
.help(helpTitle)
|
||||
.accessibilityLabel(helpTitle)
|
||||
|
|
@ -7655,6 +7658,75 @@ private struct SidebarHelpMenuButton: View {
|
|||
}
|
||||
}
|
||||
|
||||
/// Presents an NSPopover without an arrow using the shouldHideAnchor KVC trick.
|
||||
private struct ArrowlessPopoverAnchor<PopoverContent: View>: NSViewRepresentable {
|
||||
@Binding var isPresented: Bool
|
||||
let preferredEdge: NSRectEdge
|
||||
@ViewBuilder let content: () -> PopoverContent
|
||||
|
||||
func makeNSView(context: Context) -> NSView {
|
||||
NSView()
|
||||
}
|
||||
|
||||
func updateNSView(_ nsView: NSView, context: Context) {
|
||||
if isPresented {
|
||||
guard context.coordinator.popover == nil else { return }
|
||||
|
||||
let popover = NSPopover()
|
||||
popover.behavior = .semitransient
|
||||
popover.animates = true
|
||||
popover.setValue(true, forKeyPath: "shouldHideAnchor")
|
||||
popover.contentViewController = NSHostingController(rootView: content())
|
||||
popover.delegate = context.coordinator
|
||||
context.coordinator.popover = popover
|
||||
|
||||
// Show relative to a rect shifted toward the preferred edge to close
|
||||
// the gap left by the hidden arrow (~13pt arrow height).
|
||||
let arrowCompensation: CGFloat = 13
|
||||
var rect = nsView.bounds
|
||||
switch preferredEdge {
|
||||
case .maxY:
|
||||
rect = NSRect(x: rect.minX, y: rect.maxY - arrowCompensation, width: rect.width, height: arrowCompensation)
|
||||
case .minY:
|
||||
rect = NSRect(x: rect.minX, y: rect.minY, width: rect.width, height: arrowCompensation)
|
||||
case .maxX:
|
||||
rect = NSRect(x: rect.maxX - arrowCompensation, y: rect.minY, width: arrowCompensation, height: rect.height)
|
||||
case .minX:
|
||||
rect = NSRect(x: rect.minX, y: rect.minY, width: arrowCompensation, height: rect.height)
|
||||
default: break
|
||||
}
|
||||
popover.show(relativeTo: rect, of: nsView, preferredEdge: preferredEdge)
|
||||
} else {
|
||||
context.coordinator.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(isPresented: $isPresented)
|
||||
}
|
||||
|
||||
final class Coordinator: NSObject, NSPopoverDelegate {
|
||||
@Binding var isPresented: Bool
|
||||
var popover: NSPopover?
|
||||
|
||||
init(isPresented: Binding<Bool>) {
|
||||
_isPresented = isPresented
|
||||
}
|
||||
|
||||
func dismiss() {
|
||||
popover?.performClose(nil)
|
||||
popover = nil
|
||||
}
|
||||
|
||||
func popoverDidClose(_ notification: Notification) {
|
||||
popover = nil
|
||||
if isPresented {
|
||||
isPresented = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct SidebarFooterIconButtonStyle: ButtonStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
SidebarFooterIconButtonStyleBody(configuration: configuration)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue