@@ -37,6 +37,7 @@ public final class GtkBackend: AppBackend {
3737 public let menuImplementationStyle = MenuImplementationStyle . dynamicPopover
3838 public let canRevealFiles = true
3939 public let deviceClass = DeviceClass . desktop
40+ public let defaultSheetCornerRadius = 10
4041
4142 var gtkApp : Application
4243
@@ -73,21 +74,35 @@ public final class GtkBackend: AppBackend {
7374 let backend = Unmanaged < GtkBackend > . fromOpaque ( userData) . takeUnretainedValue ( )
7475 let key = OpaquePointer ( instance)
7576 guard let ctx = backend. sheetContexts [ key] else { return 1 }
76-
77+
7778 if ctx. interactiveDismissDisabled { return 1 }
78-
79+
7980 if ctx. isProgrammaticDismiss {
8081 // Suppress onDismiss for programmatic closes
8182 ctx. isProgrammaticDismiss = false
8283 return 1
8384 }
84-
85+
8586 backend. runInMainThread {
8687 ctx. onDismiss ( )
8788 }
8889 return 1
8990 }
90-
91+
92+ // C-convention thunk for key-pressed
93+ private let escapeKeyPressedThunk : @convention ( c) (
94+ UnsafeMutableRawPointer ? , guint , guint , GdkModifierType , gpointer ?
95+ ) -> gboolean = { controller, keyval, keycode, state, userData in
96+ // TRUE (1) = consume event
97+ if keyval == GDK_KEY_Escape {
98+ guard let userData else { return 1 }
99+ let box = Unmanaged < ValueBox < ( ) -> Void > > . fromOpaque ( userData) . takeUnretainedValue ( )
100+ box. value ( )
101+ return 1 // consume
102+ }
103+ return 0 // let others handle
104+ }
105+
91106 // A separate initializer to satisfy ``AppBackend``'s requirements.
92107 public convenience init ( ) {
93108 self . init ( appIdentifier: nil )
@@ -1616,34 +1631,53 @@ public final class GtkBackend: AppBackend {
16161631 public func updateSheet( _ sheet: Gtk . Window , content: Widget , onDismiss: @escaping ( ) -> Void ) {
16171632 sheet. setChild ( content)
16181633
1619- // Track per-sheet context and hook close-request once
16201634 let key : OpaquePointer = OpaquePointer ( sheet. widgetPointer)
16211635
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- }
1636+ //add a slight border to not be just a flat corner
1637+ sheet. css. set ( property: . border( color: SwiftCrossUI . Color. gray. gtkColor, width: 1 ) )
1638+
1639+ let ctx = getOrCreateSheetContext ( for: sheet)
1640+ ctx. onDismiss = onDismiss
1641+
1642+ sheet. css. set ( property: . cornerRadius( defaultSheetCornerRadius) )
1643+
1644+ if connectedCloseHandlers. insert ( key) . inserted {
1645+ let handler : GCallback = unsafeBitCast ( Self . closeRequestThunk, to: GCallback . self)
1646+ g_signal_connect_data (
1647+ UnsafeMutableRawPointer ( sheet. gobjectPointer) ,
1648+ " close-request " ,
1649+ handler,
1650+ Unmanaged . passUnretained ( self ) . toOpaque ( ) ,
1651+ nil ,
1652+ GConnectFlags ( 0 )
1653+ )
1654+
1655+ let escapeHandler = gtk_event_controller_key_new ( )
1656+ gtk_event_controller_set_propagation_phase ( escapeHandler, GTK_PHASE_BUBBLE)
1657+ g_signal_connect_data (
1658+ UnsafeMutableRawPointer ( escapeHandler) ,
1659+ " key-pressed " ,
1660+ unsafeBitCast ( escapeKeyPressedThunk, to: GCallback . self) ,
1661+ Unmanaged . passRetained ( ValueBox ( value: {
1662+ if ctx. interactiveDismissDisabled { return }
1663+ self . runInMainThread {
1664+ ctx. onDismiss ( )
1665+ }
1666+ } ) ) . toOpaque ( ) ,
1667+ { data, _ in
1668+ if let data {
1669+ Unmanaged < ValueBox < ( ) -> Void > > . fromOpaque ( data) . release ( )
1670+ }
1671+ } ,
1672+ G_CONNECT_DEFAULT
1673+ )
1674+ gtk_widget_add_controller ( sheet. widgetPointer, escapeHandler)
16411675 }
16421676 }
16431677
16441678 public func showSheet( _ sheet: Gtk . Window , window: ApplicationWindow ? ) {
16451679 sheet. isModal = true
1646- sheet. isDecorated = false // optional for a more sheet-like look
1680+ sheet. isDecorated = false
16471681 sheet. setTransient ( for: window ?? windows [ 0 ] )
16481682 sheet. present ( )
16491683 }
@@ -1664,13 +1698,24 @@ public final class GtkBackend: AppBackend {
16641698 }
16651699
16661700 public func setInteractiveDismissDisabled( for sheet: Gtk . Window , to disabled: Bool ) {
1701+ let ctx = getOrCreateSheetContext ( for: sheet)
1702+
1703+ ctx. interactiveDismissDisabled = disabled
1704+ }
1705+
1706+ public func setPresentationCornerRadius( of sheet: Gtk . Window , to radius: Double ) {
1707+ let radius = Int ( radius)
1708+ sheet. css. set ( property: . cornerRadius( radius) )
1709+ }
1710+
1711+ private func getOrCreateSheetContext( for sheet: Gtk . Window ) -> SheetContext {
16671712 let key : OpaquePointer = OpaquePointer ( sheet. widgetPointer)
16681713 if let ctx = sheetContexts [ key] {
1669- ctx . interactiveDismissDisabled = disabled
1714+ return ctx
16701715 } else {
16711716 let ctx = SheetContext ( onDismiss: { } )
1672- ctx. interactiveDismissDisabled = disabled
16731717 sheetContexts [ key] = ctx
1718+ return ctx
16741719 }
16751720 }
16761721}
@@ -1691,3 +1736,10 @@ extension Gtk.Window: SheetImplementation {
16911736 return SIMD2 ( x: self . size. width, y: self . size. height)
16921737 }
16931738}
1739+
1740+ final class ValueBox < T> {
1741+ let value : T
1742+ init ( value: T ) {
1743+ self . value = value
1744+ }
1745+ }
0 commit comments