Skip to content

Commit ed300d2

Browse files
committed
GtkBackend Sheets save
1 parent 8669cb6 commit ed300d2

File tree

1 file changed

+110
-0
lines changed

1 file changed

+110
-0
lines changed

Sources/GtkBackend/GtkBackend.swift

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

15741678
extension UnsafeMutablePointer {
@@ -1581,3 +1685,9 @@ extension UnsafeMutablePointer {
15811685
class 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

Comments
 (0)