Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 118 additions & 15 deletions Examples/Sources/WindowingExample/WindowingApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,103 @@ struct AlertDemo: View {
}
}

// kind of a stress test for the dismiss action
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// A demo displaying SwiftCrossUI's `View.sheet` modifier.

struct SheetDemo: View {
@State var isPresented = false
@State var isShortTermSheetPresented = false

var body: some View {
Button("Open Sheet") {
isPresented = true
}
Button("Show Sheet for 5s") {
isShortTermSheetPresented = true
Task {
try? await Task.sleep(nanoseconds: 1_000_000_000 * 5)
isShortTermSheetPresented = false
}
}
.sheet(isPresented: $isPresented) {
print("sheet dismissed")
} content: {
SheetBody()
.presentationDetents([.height(150), .medium, .large])
.presentationDragIndicatorVisibility(.visible)
.presentationBackground(.green)
}
.sheet(isPresented: $isShortTermSheetPresented) {
Text("I'm only here for 5s")
.padding(20)
.presentationDetents([.height(150), .medium, .large])
.presentationCornerRadius(10)
.presentationBackground(.red)
}
}

struct SheetBody: View {
@State var isPresented = false
@Environment(\.dismiss) var dismiss

var body: some View {
VStack {
Text("Nice sheet content")
.padding(20)
Button("I want more sheet") {
isPresented = true
print("should get presented")
}
Button("Dismiss") {
dismiss()
}
Spacer()
}
.sheet(isPresented: $isPresented) {
print("nested sheet dismissed")
} content: {
NestedSheetBody(dismissParent: { dismiss() })
.presentationCornerRadius(35)
}
}

struct NestedSheetBody: View {
@Environment(\.dismiss) var dismiss
var dismissParent: () -> Void
@State var showNextChild = false

var body: some View {
Text("I'm nested. Its claustrophobic in here.")
Button("New Child Sheet") {
showNextChild = true
}
.sheet(isPresented: $showNextChild) {
DoubleNestedSheetBody(dismissParent: { dismiss() })
.interactiveDismissDisabled()
}
Button("dismiss parent sheet") {
dismissParent()
}
Button("dismiss") {
dismiss()
}
}
}
struct DoubleNestedSheetBody: View {
@Environment(\.dismiss) var dismiss
var dismissParent: () -> Void

var body: some View {
Text("I'm nested. Its claustrophobic in here.")
Button("dismiss parent sheet") {
dismissParent()
}
Button("dismiss") {
dismiss()
}
}
}
}
}

@main
@HotReloadable
struct WindowingApp: App {
Expand Down Expand Up @@ -92,6 +189,11 @@ struct WindowingApp: App {
Divider()

AlertDemo()

Divider()

SheetDemo()
.padding(.bottom, 20)
}
.padding(20)
}
Expand All @@ -108,23 +210,24 @@ struct WindowingApp: App {
}
}
}

WindowGroup("Secondary window") {
#hotReloadable {
Text("This a secondary window!")
.padding(10)
#if !os(iOS)
WindowGroup("Secondary window") {
#hotReloadable {
Text("This a secondary window!")
.padding(10)
}
}
}
.defaultSize(width: 200, height: 200)
.windowResizability(.contentMinSize)
.defaultSize(width: 200, height: 200)
.windowResizability(.contentMinSize)

WindowGroup("Tertiary window") {
#hotReloadable {
Text("This a tertiary window!")
.padding(10)
WindowGroup("Tertiary window") {
#hotReloadable {
Text("This a tertiary window!")
.padding(10)
}
}
}
.defaultSize(width: 200, height: 200)
.windowResizability(.contentMinSize)
.defaultSize(width: 200, height: 200)
.windowResizability(.contentMinSize)
#endif
}
}
2 changes: 1 addition & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

108 changes: 108 additions & 0 deletions Sources/AppKitBackend/AppKitBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public final class AppKitBackend: AppBackend {
public typealias Menu = NSMenu
public typealias Alert = NSAlert
public typealias Path = NSBezierPath
public typealias Sheet = NSCustomSheet

public let defaultTableRowContentHeight = 20
public let defaultTableCellVerticalPadding = 4
Expand Down Expand Up @@ -1685,6 +1686,113 @@ public final class AppKitBackend: AppBackend {
let request = URLRequest(url: url)
webView.load(request)
}

public func createSheet() -> NSCustomSheet {
// Initialize with a default contentRect, similar to window creation (lines 58-68)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to method name instead of lines (lines may change)

let sheet = NSCustomSheet(
contentRect: NSRect(
x: 0,
y: 0,
width: 400, // Default width
height: 300 // Default height
),
styleMask: [.titled, .closable],
backing: .buffered,
defer: true
)
return sheet
}

public func updateSheet(
_ sheet: NSCustomSheet, content: NSView, onDismiss: @escaping () -> Void
) {
let contentSize = naturalSize(of: content)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Content size should be passed in from SwiftCrossUI because even if this works for AppKitBackend, not all backends will be able to infer the sizing that SwiftCrossUI has assigned to their sheet content.

This may resolve the Gtk issues you were facing.


let width = max(contentSize.x, 10)
let height = max(contentSize.y, 10)
sheet.setContentSize(NSSize(width: width, height: height))

sheet.contentView = content
sheet.onDismiss = onDismiss
}

public func showSheet(_ sheet: NSCustomSheet, window: NSCustomWindow?) {
guard let window else {
print("warning: Cannot show sheet without a parent window")
return
}
// critical sheets stack
// beginSheet only shows a nested
// sheet after its parent gets dismissed
Comment on lines +1724 to +1726
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Critical sheets stack. beginSheet only shows a nested sheet after its parent gets dismissed (wrapped at around 80 chars if necessary

window.beginCriticalSheet(sheet)
}

public func dismissSheet(_ sheet: NSCustomSheet, window: NSCustomWindow?) {
if let window {
window.endSheet(sheet)
} else {
NSApplication.shared.stopModal()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary if showSheet never shows sheets without parent windows?

}
}

public func setPresentationBackground(of sheet: NSCustomSheet, to color: Color) {
let backgroundView = NSView()
backgroundView.wantsLayer = true
backgroundView.layer?.backgroundColor = color.nsColor.cgColor

Comment on lines +1739 to +1742
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it work to set the backgroundColor of the window instead?

if let existingContentView = sheet.contentView {
let container = NSView()
container.translatesAutoresizingMaskIntoConstraints = false

container.addSubview(backgroundView)
backgroundView.translatesAutoresizingMaskIntoConstraints = false
backgroundView.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive =
true
backgroundView.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
backgroundView.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive =
true
backgroundView.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true

container.addSubview(existingContentView)
existingContentView.translatesAutoresizingMaskIntoConstraints = false
existingContentView.leadingAnchor.constraint(equalTo: container.leadingAnchor)
.isActive = true
existingContentView.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
existingContentView.trailingAnchor.constraint(equalTo: container.trailingAnchor)
.isActive = true
existingContentView.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive =
true

sheet.contentView = container
}
}

public func setInteractiveDismissDisabled(for sheet: NSCustomSheet, to disabled: Bool) {
sheet.interactiveDismissDisabled = disabled
}
}

public final class NSCustomSheet: NSCustomWindow, NSWindowDelegate, SheetImplementation {
public var sheetSize: SIMD2<Int> {
guard let size = self.contentView?.frame.size else {
return SIMD2(x: 0, y: 0)
}
return SIMD2(x: Int(size.width), y: Int(size.height))
}
public var onDismiss: (() -> Void)?

public var interactiveDismissDisabled: Bool = false

public func dismiss() {
onDismiss?()
self.contentViewController?.dismiss(self)
}

@objc override public func cancelOperation(_ sender: Any?) {
if !interactiveDismissDisabled {
dismiss()
}
}
}

final class NSCustomTapGestureTarget: NSView {
Expand Down
7 changes: 7 additions & 0 deletions Sources/Gtk3Backend/Gtk3Backend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public final class Gtk3Backend: AppBackend {
public typealias Widget = Gtk3.Widget
public typealias Menu = Gtk3.Menu
public typealias Alert = Gtk3.MessageDialog
public typealias Sheet = Gtk3.Window

public final class Path {
var path: SwiftCrossUI.Path?
Expand Down Expand Up @@ -1516,3 +1517,9 @@ struct Gtk3Error: LocalizedError {
"gerror: code=\(code), domain=\(domain), message=\(message)"
}
}

extension Gtk3.Window: SheetImplementation {
public var sheetSize: SIMD2<Int> {
SIMD2(x: size.width, y: size.height)
}
}
Loading
Loading