Skip to content

Commit

Permalink
- Added a custom view modifier to display popovers without animations
Browse files Browse the repository at this point in the history
- Updated existing popovers to use the new instant popover modifier
  • Loading branch information
Kihron committed Oct 27, 2024
1 parent 9d743ec commit 85dc011
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CodeEdit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,7 @@
B6CFD8112C20A8EE00E63F1A /* NSFont+WithWeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6CFD8102C20A8EE00E63F1A /* NSFont+WithWeight.swift */; };
B6D7EA592971078500301FAC /* InspectorSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D7EA582971078500301FAC /* InspectorSection.swift */; };
B6D7EA5C297107DD00301FAC /* InspectorField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D7EA5B297107DD00301FAC /* InspectorField.swift */; };
B6DCDAC62CCDE2B90099FBF9 /* InstantPopoverModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DCDAC52CCDE2B90099FBF9 /* InstantPopoverModifier.swift */; };
B6E41C7029DD157F0088F9F4 /* AccountsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C6F29DD157F0088F9F4 /* AccountsSettingsView.swift */; };
B6E41C7429DD40010088F9F4 /* View+HideSidebarToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C7329DD40010088F9F4 /* View+HideSidebarToggle.swift */; };
B6E41C7929DE02800088F9F4 /* AccountSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C7829DE02800088F9F4 /* AccountSelectionView.swift */; };
Expand Down Expand Up @@ -1250,6 +1251,7 @@
B6CFD8102C20A8EE00E63F1A /* NSFont+WithWeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSFont+WithWeight.swift"; sourceTree = "<group>"; };
B6D7EA582971078500301FAC /* InspectorSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorSection.swift; sourceTree = "<group>"; };
B6D7EA5B297107DD00301FAC /* InspectorField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorField.swift; sourceTree = "<group>"; };
B6DCDAC52CCDE2B90099FBF9 /* InstantPopoverModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPopoverModifier.swift; sourceTree = "<group>"; };
B6E41C6F29DD157F0088F9F4 /* AccountsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsView.swift; sourceTree = "<group>"; };
B6E41C7329DD40010088F9F4 /* View+HideSidebarToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+HideSidebarToggle.swift"; sourceTree = "<group>"; };
B6E41C7829DE02800088F9F4 /* AccountSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSelectionView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2068,6 +2070,7 @@
6CBA0D502A1BF524002C6FAA /* SegmentedControlImproved.swift */,
587B9D8C29300ABD00AC7927 /* SettingsTextEditor.swift */,
587B9D8F29300ABD00AC7927 /* ToolbarBranchPicker.swift */,
B6DCDAC52CCDE2B90099FBF9 /* InstantPopoverModifier.swift */,
2897E1C62979A29200741E32 /* TrackableScrollView.swift */,
B60718302B15A9A3009CDAB4 /* CEOutlineGroup.swift */,
);
Expand Down Expand Up @@ -4032,6 +4035,7 @@
6CFF967429BEBCC300182D6F /* FindCommands.swift in Sources */,
587B9E6529301D8F00AC7927 /* GitLabGroupAccess.swift in Sources */,
6C91D57229B176FF0059A90D /* EditorManager.swift in Sources */,
B6DCDAC62CCDE2B90099FBF9 /* InstantPopoverModifier.swift in Sources */,
6C82D6BC29C00CD900495C54 /* FirstResponderPropertyWrapper.swift in Sources */,
58D01C9B293167DC00C5B6B4 /* CodeEditKeychainConstants.swift in Sources */,
B640A99E29E2184700715F20 /* SettingsForm.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ struct SchemeDropDownView: View {
.onHover(perform: { hovering in
self.isHoveringScheme = hovering
})
.popover(isPresented: $isSchemePopOverPresented, arrowEdge: .bottom) {
.instantPopover(isPresented: $isSchemePopOverPresented, arrowEdge: .bottom) {
VStack(alignment: .leading, spacing: 0) {
WorkspaceMenuItemView(
workspaceFileManager: workspaceFileManager,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct TaskDropDownView: View {
.onHover { hovering in
self.isHoveringTasks = hovering
}
.popover(isPresented: $isTaskPopOverPresented, arrowEdge: .bottom) {
.instantPopover(isPresented: $isTaskPopOverPresented, arrowEdge: .bottom) {
taskPopoverContent
}
.onTapGesture {
Expand Down
132 changes: 132 additions & 0 deletions CodeEdit/Features/CodeEditUI/Views/InstantPopoverModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//
// InstantPopoverModifier.swift
// CodeEdit
//
// Created by Kihron on 10/26/24.
//

import SwiftUI

struct InstantPopoverModifier<PopoverContent: View>: ViewModifier {
@Binding var isPresented: Bool
let arrowEdge: Edge
let popoverContent: PopoverContent

func body(content: Content) -> some View {
content
.background(
PopoverPresenter(
isPresented: $isPresented,
arrowEdge: arrowEdge,
contentView: popoverContent
)
)
}
}

struct PopoverPresenter<ContentView: View>: NSViewRepresentable {
@Binding var isPresented: Bool
let arrowEdge: Edge
let contentView: ContentView

func makeNSView(context: Context) -> NSView { NSView() }

func updateNSView(_ nsView: NSView, context: Context) {
if isPresented, context.coordinator.popover == nil {
let popover = NSPopover()
popover.animates = false
let hostingController = NSHostingController(rootView: contentView)

hostingController.view.layoutSubtreeIfNeeded()
let contentSize = hostingController.view.fittingSize
popover.contentSize = contentSize

popover.contentViewController = hostingController
popover.delegate = context.coordinator
popover.behavior = .semitransient

let nsRectEdge = edgeToNSRectEdge(arrowEdge)
popover.show(relativeTo: nsView.bounds, of: nsView, preferredEdge: nsRectEdge)
context.coordinator.popover = popover

if let parentWindow = nsView.window {
context.coordinator.startObservingWindow(parentWindow)
}
} else if !isPresented, let popover = context.coordinator.popover {
popover.close()
context.coordinator.popover = nil
}
}

func makeCoordinator() -> Coordinator {
Coordinator(isPresented: $isPresented)
}

class Coordinator: NSObject, NSPopoverDelegate {
@Binding var isPresented: Bool
var popover: NSPopover?

init(isPresented: Binding<Bool>) {
_isPresented = isPresented
super.init()
}

func startObservingWindow(_ window: NSWindow) {
/// Observe when the window loses focus
NotificationCenter.default.addObserver(
forName: NSWindow.didResignKeyNotification,
object: window,
queue: .main
) { [weak self] _ in
guard let self = self else { return }
/// The parent window is no longer focused, close the popover
DispatchQueue.main.async {
self.isPresented = false
self.popover?.close()
}
}
}

func popoverWillClose(_ notification: Notification) {
DispatchQueue.main.async {
self.isPresented = false
}
}

func popoverDidClose(_ notification: Notification) {
popover = nil
}
}

private func edgeToNSRectEdge(_ edge: Edge) -> NSRectEdge {
switch edge {
case .top: return .minY
case .leading: return .minX
case .bottom: return .maxY
case .trailing: return .maxX
}
}
}

extension View {

/// A custom view modifier that presents a popover attached to the view with no animation.
/// - Parameters:
/// - isPresented: A binding to whether the popover is presented.
/// - arrowEdge: The edge of the view that the popover points to. Defaults to `.bottom`.
/// - content: A closure returning the content of the popover.
/// - Returns: A view that presents a popover when `isPresented` is `true`.
func instantPopover<Content: View>(
isPresented: Binding<Bool>,
arrowEdge: Edge = .bottom,
@ViewBuilder content: () -> Content
) -> some View {
self.modifier(
InstantPopoverModifier(
isPresented: isPresented,
arrowEdge: arrowEdge,
popoverContent: content()
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ struct HistoryInspectorItemView: View {
} set: { newValue in
if newValue {
selection = commit
} else {
} else if selection == commit {
selection = nil
}
}
}

var body: some View {
CommitListItemView(commit: commit, showRef: false)
.popover(isPresented: showPopup, arrowEdge: .leading) {
.instantPopover(isPresented: showPopup, arrowEdge: .leading) {
HistoryPopoverView(commit: commit)
}
}
Expand Down

0 comments on commit 85dc011

Please sign in to comment.