Add onClose to windows
Some checks failed
Deploy Docs / publish (push) Has been cancelled
SwiftLint / SwiftLint (push) Has been cancelled

This commit is contained in:
david-swift 2025-10-03 23:07:41 +02:00
parent 1bc36e0759
commit e430031bbc
5 changed files with 128 additions and 9 deletions

View File

@ -11,7 +11,7 @@ import CAdw
public class SignalData { public class SignalData {
/// The closure. /// The closure.
public var closure: ([Any?]) -> Void public var closure: ([Any?]) -> Any?
/// Destroy the class. /// Destroy the class.
public var selfDestruction: (() -> Void)? public var selfDestruction: (() -> Void)?
@ -62,11 +62,19 @@ public class SignalData {
} }
} }
/// The closure as a C handler with a return value.
var boolHandler: @convention(c) (UnsafeMutableRawPointer, UnsafeMutableRawPointer) -> Bool {
{ _, data in
let data = unsafeBitCast(data, to: SignalData.self)
return data.closure([]) as? Bool ?? false
}
}
/// Initialize the signal data. /// Initialize the signal data.
/// - Parameters: /// - Parameters:
/// - closure: The signal's closure. /// - closure: The signal's closure.
/// - destroy: The self destruction. /// - destroy: The self destruction.
public convenience init(closure: @escaping () -> Void, destroy: (() -> Void)? = nil) { public convenience init(closure: @escaping () -> Any?, destroy: (() -> Void)? = nil) {
self.init(closure: { _ in closure() }, destroy: destroy) self.init(closure: { _ in closure() }, destroy: destroy)
} }
@ -74,19 +82,46 @@ public class SignalData {
/// - Parameters: /// - Parameters:
/// - closure: The signal's closure. /// - closure: The signal's closure.
/// - destroy: The self destruction. /// - destroy: The self destruction.
public init(closure: @escaping ([Any]) -> Void, destroy: (() -> Void)? = nil) { public convenience init(closure: @escaping () -> Void, destroy: (() -> Void)? = nil) {
self.init(closure: { _ in closure(); return nil }, destroy: destroy)
}
/// Initialize the signal data.
/// - Parameters:
/// - closure: The signal's closure.
/// - destroy: The self destruction.
public init(closure: @escaping ([Any]) -> Any?, destroy: (() -> Void)? = nil) {
self.closure = closure self.closure = closure
self.selfDestruction = destroy self.selfDestruction = destroy
} }
/// The return type.
public enum ReturnType {
/// Returns a boolean.
case bool
}
/// Connect the signal data to a signal. /// Connect the signal data to a signal.
/// - Parameters: /// - Parameters:
/// - pointer: The pointer to the object which holds the signal. /// - pointer: The pointer to the object which holds the signal.
/// - signal: The signal's name. /// - signal: The signal's name.
/// - argCount: The number of arguments. /// - argCount: The number of arguments.
public func connect(pointer: UnsafeMutableRawPointer?, signal: String, argCount: Int = 0) { /// - return: The return type.
public func connect(
pointer: UnsafeMutableRawPointer?,
signal: String,
argCount: Int = 0,
return: ReturnType? = nil
) {
let callback: GCallback let callback: GCallback
if argCount >= 3 { if let `return` {
switch `return` {
case .bool:
callback = unsafeBitCast(boolHandler, to: GCallback.self)
}
} else if argCount >= 3 {
callback = unsafeBitCast(fiveParamsHandler, to: GCallback.self) callback = unsafeBitCast(fiveParamsHandler, to: GCallback.self)
} else if argCount == 2 { } else if argCount == 2 {
callback = unsafeBitCast(fourParamsHandler, to: GCallback.self) callback = unsafeBitCast(fourParamsHandler, to: GCallback.self)

View File

@ -48,7 +48,30 @@ extension Storage {
pointer: OpaquePointer? = nil, pointer: OpaquePointer? = nil,
handler: @escaping () -> Void handler: @escaping () -> Void
) { ) {
connectSignal(name: name, id: id, argCount: argCount, pointer: pointer) { _ in connectSignal(name: name, id: id, argCount: argCount, pointer: pointer) {
handler()
return nil
}
}
/// Connect a handler to a signal.
/// - Parameters:
/// - name: The signal's name.
/// - id: The handlers id to separate form others connecting to the signal.
/// - connectFlags: The GConnectFlags.
/// - argCount: The number of additional arguments (without the first and the last one).
/// - return: The return type.
/// - pointer: A custom pointer instead of the stored one.
/// - handler: The signal's handler.
public func connectSignal(
name: String,
id: String = "",
argCount: Int = 0,
return: SignalData.ReturnType? = nil,
pointer: OpaquePointer? = nil,
handler: @escaping () -> Any?
) {
connectSignal(name: name, id: id, argCount: argCount, return: `return`, pointer: pointer) { _ in
handler() handler()
} }
} }
@ -66,13 +89,42 @@ extension Storage {
argCount: Int = 0, argCount: Int = 0,
pointer: OpaquePointer? = nil, pointer: OpaquePointer? = nil,
handler: @escaping ([Any]) -> Void handler: @escaping ([Any]) -> Void
) {
connectSignal(name: name, id: id, argCount: argCount, pointer: pointer) { args in
handler(args)
return nil
}
}
/// Connect a handler to a signal.
/// - Parameters:
/// - name: The signal's name.
/// - id: The handlers id to separate form others connecting to the signal.
/// - argCount: The number of additional arguments (without the first and the last one).
/// - return: The return type.
/// - pointer: A custom pointer instead of the stored one.
/// - handler: The signal's handler.
public func connectSignal(
name: String,
id: String = "",
argCount: Int = 0,
return: SignalData.ReturnType? = nil,
pointer: OpaquePointer? = nil,
handler: @escaping ([Any]) -> Any?
) { ) {
if let data = fields[name + id] as? SignalData { if let data = fields[name + id] as? SignalData {
data.closure = handler data.closure = handler
} else { } else {
let data = SignalData(closure: handler) { [self] in fields[name + id] = nil } let data = SignalData(closure: handler) { [self] in fields[name + id] = nil }
fields[name + id] = data fields[name + id] = data
data.connect(pointer: (pointer ?? opaquePointer)?.cast(), signal: name, argCount: argCount) data.connect(
pointer: (
pointer ?? opaquePointer ?? ((self as? SceneStorage)?.pointer as? AdwaitaWindow)?.pointer?.opaque()
)?.cast(),
signal: name,
argCount: argCount,
return: `return`
)
} }
} }

View File

@ -47,6 +47,9 @@ public struct Window: AdwaitaSceneElement {
var maximized: Binding<Bool>? var maximized: Binding<Bool>?
/// Whether the window uses the development style. /// Whether the window uses the development style.
var devel: Bool? var devel: Bool?
/// Run this function when the window should close.
/// Return true to stop the closing process.
var onClose: (() -> Bool)?
/// Create a window type with a certain identifier and user interface. /// Create a window type with a certain identifier and user interface.
/// - Parameters: /// - Parameters:
@ -136,6 +139,9 @@ public struct Window: AdwaitaSceneElement {
if minWidth != nil || minHeight != nil { if minWidth != nil || minHeight != nil {
gtk_widget_set_size_request(window.pointer?.cast(), .init(minWidth ?? -1), .init(minHeight ?? -1)) gtk_widget_set_size_request(window.pointer?.cast(), .init(minWidth ?? -1), .init(minHeight ?? -1))
} }
storage.connectSignal(name: "close-request", id: "on-close", return: .bool) {
(storage.fields["on-close"] as? (() -> Bool))?() ?? false
}
update(storage, app: app, updateProperties: true) update(storage, app: app, updateProperties: true)
return storage return storage
} }
@ -246,6 +252,9 @@ public struct Window: AdwaitaSceneElement {
gtk_window_unmaximize(window.pointer?.cast()) gtk_window_unmaximize(window.pointer?.cast())
} }
} }
if let onClose {
storage.fields["on-close"] = onClose
}
storage.previousState = self storage.previousState = self
} }
@ -373,9 +382,19 @@ public struct Window: AdwaitaSceneElement {
return newSelf return newSelf
} }
/// Run this closure when the window should be closed.
/// - Parameter onClose: The closure. Return true to cancel the closing process.
/// - Returns: The window.
///
/// This does not get executed if the user quits the whole application.
public func onClose(onClose: @escaping () -> Bool) -> Self {
var newSelf = self
newSelf.onClose = onClose
return newSelf
}
} }
// swiftlint:enable discouraged_optional_collection // swiftlint:enable discouraged_optional_collection
/// An AdwApplicationWindow. /// An AdwApplicationWindow.
public typealias AdwaitaWindow = Window.AdwaitaWindow public typealias AdwaitaWindow = Window.AdwaitaWindow

View File

@ -91,6 +91,8 @@ struct Demo: App {
@State private var about = false @State private var about = false
@State private var preferences = false @State private var preferences = false
@State private var title: WindowName = .demo @State private var title: WindowName = .demo
@State private var closeAlert = false
@State private var destroy = false
var window: AdwaitaWindow var window: AdwaitaWindow
var app: AdwaitaApp! var app: AdwaitaApp!
var pictureURL: URL? var pictureURL: URL?
@ -172,6 +174,16 @@ struct Demo: App {
.title("Extra Action") .title("Extra Action")
} }
} }
.alertDialog(
visible: $closeAlert,
heading: "Close this Window?",
body: "Nothing will be lost. This is a demo."
)
.response("Cancel", role: .close) { }
.response("Close", appearance: .suggested, role: .default) {
destroy = true
window.close()
}
} }
var menu: AnyView { var menu: AnyView {
@ -200,6 +212,7 @@ struct Demo: App {
window window
.size(width: $width, height: $height) .size(width: $width, height: $height)
.maximized($maximized) .maximized($maximized)
.onClose { closeAlert = !destroy; return !destroy }
} }
} }

View File

@ -1,7 +1,7 @@
{ {
"app-id": "io.github.AparokshaUI.Demo", "app-id": "io.github.AparokshaUI.Demo",
"runtime": "org.gnome.Platform", "runtime": "org.gnome.Platform",
"runtime-version": "48", "runtime-version": "49",
"sdk": "org.gnome.Sdk", "sdk": "org.gnome.Sdk",
"sdk-extensions": [ "sdk-extensions": [
"org.freedesktop.Sdk.Extension.swift6" "org.freedesktop.Sdk.Extension.swift6"