diff --git a/Sources/Core/Model/Signals/SignalData.swift b/Sources/Core/Model/Signals/SignalData.swift index 0a80724..75dd76b 100644 --- a/Sources/Core/Model/Signals/SignalData.swift +++ b/Sources/Core/Model/Signals/SignalData.swift @@ -11,7 +11,7 @@ import CAdw public class SignalData { /// The closure. - public var closure: ([Any?]) -> Void + public var closure: ([Any?]) -> Any? /// Destroy the class. 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. /// - Parameters: /// - closure: The signal's closure. /// - 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) } @@ -74,19 +82,46 @@ public class SignalData { /// - Parameters: /// - closure: The signal's closure. /// - 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.selfDestruction = destroy } + /// The return type. + public enum ReturnType { + + /// Returns a boolean. + case bool + + } + /// Connect the signal data to a signal. /// - Parameters: /// - pointer: The pointer to the object which holds the signal. /// - signal: The signal's name. /// - 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 - 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) } else if argCount == 2 { callback = unsafeBitCast(fourParamsHandler, to: GCallback.self) diff --git a/Sources/Core/Model/Storage.swift b/Sources/Core/Model/Storage.swift index 16361ee..5cc1e6c 100644 --- a/Sources/Core/Model/Storage.swift +++ b/Sources/Core/Model/Storage.swift @@ -48,7 +48,30 @@ extension Storage { pointer: OpaquePointer? = nil, 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() } } @@ -66,13 +89,42 @@ extension Storage { argCount: Int = 0, pointer: OpaquePointer? = nil, 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 { data.closure = handler } else { let data = SignalData(closure: handler) { [self] in fields[name + id] = nil } 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` + ) } } diff --git a/Sources/Core/Window/Window.swift b/Sources/Core/Window/Window.swift index efa989c..cd0066f 100644 --- a/Sources/Core/Window/Window.swift +++ b/Sources/Core/Window/Window.swift @@ -47,6 +47,9 @@ public struct Window: AdwaitaSceneElement { var maximized: Binding? /// Whether the window uses the development style. 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. /// - Parameters: @@ -136,6 +139,9 @@ public struct Window: AdwaitaSceneElement { if minWidth != nil || minHeight != nil { 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) return storage } @@ -246,6 +252,9 @@ public struct Window: AdwaitaSceneElement { gtk_window_unmaximize(window.pointer?.cast()) } } + if let onClose { + storage.fields["on-close"] = onClose + } storage.previousState = self } @@ -373,9 +382,19 @@ public struct Window: AdwaitaSceneElement { 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 - /// An AdwApplicationWindow. public typealias AdwaitaWindow = Window.AdwaitaWindow diff --git a/Sources/Demo/Demo.swift b/Sources/Demo/Demo.swift index a078eab..fbbc5eb 100644 --- a/Sources/Demo/Demo.swift +++ b/Sources/Demo/Demo.swift @@ -91,6 +91,8 @@ struct Demo: App { @State private var about = false @State private var preferences = false @State private var title: WindowName = .demo + @State private var closeAlert = false + @State private var destroy = false var window: AdwaitaWindow var app: AdwaitaApp! var pictureURL: URL? @@ -172,6 +174,16 @@ struct Demo: App { .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 { @@ -200,6 +212,7 @@ struct Demo: App { window .size(width: $width, height: $height) .maximized($maximized) + .onClose { closeAlert = !destroy; return !destroy } } } diff --git a/io.github.AparokshaUI.Demo.json b/io.github.AparokshaUI.Demo.json index f76f81a..08a8578 100644 --- a/io.github.AparokshaUI.Demo.json +++ b/io.github.AparokshaUI.Demo.json @@ -1,7 +1,7 @@ { "app-id": "io.github.AparokshaUI.Demo", "runtime": "org.gnome.Platform", - "runtime-version": "48", + "runtime-version": "49", "sdk": "org.gnome.Sdk", "sdk-extensions": [ "org.freedesktop.Sdk.Extension.swift6"