@@ -22,6 +22,7 @@ public final class GtkBackend: AppBackend {
2222 public typealias Widget = Gtk . Widget
2323 public typealias Menu = Gtk . PopoverMenu
2424 public typealias Alert = Gtk . MessageDialog
25+ public typealias Sheet = Gtk . Window
2526
2627 public final class Path {
2728 var path : SwiftCrossUI . Path ?
@@ -48,6 +49,45 @@ public final class GtkBackend: AppBackend {
4849 /// precreated window until it gets 'created' via `createWindow`.
4950 var windows : [ Window ] = [ ]
5051
52+ // Sheet management (close-request, programmatic dismiss, interactive lock)
53+ private final class SheetContext {
54+ var onDismiss : ( ) -> Void
55+ var isProgrammaticDismiss : Bool = false
56+ var interactiveDismissDisabled : Bool = false
57+
58+ init ( onDismiss: @escaping ( ) -> Void ) {
59+ self . onDismiss = onDismiss
60+ }
61+ }
62+
63+ private var sheetContexts : [ OpaquePointer : SheetContext ] = [ : ]
64+ private var connectedCloseHandlers : Set < OpaquePointer > = [ ]
65+
66+ // C thunk for GtkWindow::close-request
67+ private static let closeRequestThunk :
68+ @convention ( c) (
69+ UnsafeMutableRawPointer ? , UnsafeMutableRawPointer ?
70+ ) -> Int32 = { instance, userData in
71+ // TRUE (1) = consume event (prevent native close)
72+ guard let instance, let userData else { return 1 }
73+ let backend = Unmanaged < GtkBackend > . fromOpaque ( userData) . takeUnretainedValue ( )
74+ let key = OpaquePointer ( instance)
75+ guard let ctx = backend. sheetContexts [ key] else { return 1 }
76+
77+ if ctx. interactiveDismissDisabled { return 1 }
78+
79+ if ctx. isProgrammaticDismiss {
80+ // Suppress onDismiss for programmatic closes
81+ ctx. isProgrammaticDismiss = false
82+ return 1
83+ }
84+
85+ backend. runInMainThread {
86+ ctx. onDismiss ( )
87+ }
88+ return 1
89+ }
90+
5191 // A separate initializer to satisfy ``AppBackend``'s requirements.
5292 public convenience init ( ) {
5393 self . init ( appIdentifier: nil )
@@ -1569,6 +1609,70 @@ public final class GtkBackend: AppBackend {
15691609
15701610 return properties
15711611 }
1612+ public func createSheet( ) -> Gtk . Window {
1613+ return Gtk . Window ( )
1614+ }
1615+
1616+ public func updateSheet( _ sheet: Gtk . Window , content: Widget , onDismiss: @escaping ( ) -> Void ) {
1617+ sheet. setChild ( content)
1618+
1619+ // Track per-sheet context and hook close-request once
1620+ let key : OpaquePointer = OpaquePointer ( sheet. widgetPointer)
1621+
1622+ if let ctx = sheetContexts [ key] {
1623+ // Update onDismiss if sheet already tracked
1624+ ctx. onDismiss = onDismiss
1625+ } else {
1626+ // First-time setup: store context and connect signal
1627+ let ctx = SheetContext ( onDismiss: onDismiss)
1628+ sheetContexts [ key] = ctx
1629+
1630+ if connectedCloseHandlers. insert ( key) . inserted {
1631+ let handler : GCallback = unsafeBitCast ( Self . closeRequestThunk, to: GCallback . self)
1632+ g_signal_connect_data (
1633+ UnsafeMutableRawPointer ( sheet. gobjectPointer) ,
1634+ " close-request " ,
1635+ handler,
1636+ Unmanaged . passUnretained ( self ) . toOpaque ( ) ,
1637+ nil ,
1638+ GConnectFlags ( 0 )
1639+ )
1640+ }
1641+ }
1642+ }
1643+
1644+ public func showSheet( _ sheet: Gtk . Window , window: ApplicationWindow ? ) {
1645+ sheet. isModal = true
1646+ sheet. isDecorated = false // optional for a more sheet-like look
1647+ sheet. setTransient ( for: window ?? windows [ 0 ] )
1648+ sheet. present ( )
1649+ }
1650+
1651+ public func dismissSheet( _ sheet: Gtk . Window , window: ApplicationWindow ? ) {
1652+ let key : OpaquePointer = OpaquePointer ( sheet. widgetPointer)
1653+ if let ctx = sheetContexts [ key] {
1654+ // Suppress onDismiss when closing programmatically
1655+ ctx. isProgrammaticDismiss = true
1656+ }
1657+ sheet. destroy ( )
1658+ sheetContexts. removeValue ( forKey: key)
1659+ connectedCloseHandlers. remove ( key)
1660+ }
1661+
1662+ public func setPresentationBackground( of sheet: Gtk . Window , to color: SwiftCrossUI . Color ) {
1663+ sheet. css. set ( properties: [ . backgroundColor( color. gtkColor) ] )
1664+ }
1665+
1666+ public func setInteractiveDismissDisabled( for sheet: Gtk . Window , to disabled: Bool ) {
1667+ let key : OpaquePointer = OpaquePointer ( sheet. widgetPointer)
1668+ if let ctx = sheetContexts [ key] {
1669+ ctx. interactiveDismissDisabled = disabled
1670+ } else {
1671+ let ctx = SheetContext ( onDismiss: { } )
1672+ ctx. interactiveDismissDisabled = disabled
1673+ sheetContexts [ key] = ctx
1674+ }
1675+ }
15721676}
15731677
15741678extension UnsafeMutablePointer {
@@ -1581,3 +1685,9 @@ extension UnsafeMutablePointer {
15811685class CustomListBox : ListBox {
15821686 var cachedSelection : Int ? = nil
15831687}
1688+
1689+ extension Gtk . Window : SheetImplementation {
1690+ public var sheetSize : SIMD2 < Int > {
1691+ return SIMD2 ( x: self . size. width, y: self . size. height)
1692+ }
1693+ }
0 commit comments