Add onClose to windows

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 {
/// 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)

View File

@ -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`
)
}
}

View File

@ -47,6 +47,9 @@ public struct Window: AdwaitaSceneElement {
var maximized: Binding<Bool>?
/// 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

View File

@ -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 }
}
}

View File

@ -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"