diff --git a/Sources/Gtk/Utility/GActionMap.swift b/Sources/Gtk/Utility/GActionMap.swift index b2721c55513..d43098ed3ca 100644 --- a/Sources/Gtk/Utility/GActionMap.swift +++ b/Sources/Gtk/Utility/GActionMap.swift @@ -8,9 +8,4 @@ extension GActionMap { public func addAction(_ action: any GAction) { g_action_map_add_action(actionMapPointer, action.actionPointer) } - - public func addAction(named name: String, action: @escaping () -> Void) { - let simpleAction = GSimpleAction(name: name, action: action) - addAction(simpleAction) - } } diff --git a/Sources/Gtk/Utility/GSimpleAction.swift b/Sources/Gtk/Utility/GSimpleAction.swift index 12a2d3db9ab..b201f161741 100644 --- a/Sources/Gtk/Utility/GSimpleAction.swift +++ b/Sources/Gtk/Utility/GSimpleAction.swift @@ -9,42 +9,63 @@ public class GSimpleAction: GAction, GObjectRepresentable { @GObjectProperty(named: "enabled") var enabled: Bool - private class Action { - var run: () -> Void - - init(_ action: @escaping () -> Void) { - run = action - } + private class Box { + var value: T + init(_ value: T) { self.value = value } } public init(name: String, action: @escaping () -> Void) { actionPointer = g_simple_action_new(name, nil) - let wrappedAction = Action(action) + connectActionSignal(Box(action)) { _, _, data in + let action = Unmanaged Void>>.fromOpaque(data).takeUnretainedValue() + action.value() + } + } + + public init(name: String, state: Bool, action: @escaping (Bool) -> Void) { + actionPointer = g_simple_action_new_stateful( + name, + nil, + g_variant_new_boolean(state.toGBoolean()) + ) + + connectActionSignal(Box(action)) { actionPointer, _, data in + // Get the current state... + let stateVariant = g_action_get_state(OpaquePointer(actionPointer)) + let state = g_variant_get_boolean(stateVariant).toBool() - let handler: + // ...toggle it... + let newState = !state + g_simple_action_set_state( + OpaquePointer(actionPointer), + g_variant_new_boolean(newState.toGBoolean()) + ) + + // ...then get the action and call it with the new state. + let action = Unmanaged Void>>.fromOpaque(data).takeUnretainedValue() + action.value(newState) + } + } + + private func connectActionSignal( + _ value: some AnyObject, + handler: @convention(c) ( UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer - ) -> Void = - { _, _, data in - let action = Unmanaged.fromOpaque(data) - .takeUnretainedValue() - action.run() - } - + ) -> Void + ) { g_signal_connect_data( UnsafeMutableRawPointer(actionPointer), "activate", gCallback(handler), - Unmanaged.passRetained(wrappedAction).toOpaque(), + Unmanaged.passRetained(value).toOpaque(), { data, _ in - Unmanaged.fromOpaque(data!).release() + Unmanaged.fromOpaque(data!).release() }, G_CONNECT_AFTER ) } - - public func registerSignals() {} } diff --git a/Sources/Gtk3/Utility/GActionMap.swift b/Sources/Gtk3/Utility/GActionMap.swift index 6d38cdc43c8..d4ac14d4098 100644 --- a/Sources/Gtk3/Utility/GActionMap.swift +++ b/Sources/Gtk3/Utility/GActionMap.swift @@ -8,9 +8,4 @@ extension GActionMap { public func addAction(_ action: any GAction) { g_action_map_add_action(actionMapPointer, action.actionPointer) } - - public func addAction(named name: String, action: @escaping () -> Void) { - let simpleAction = GSimpleAction(name: name, action: action) - addAction(simpleAction) - } } diff --git a/Sources/Gtk3/Utility/GSimpleAction.swift b/Sources/Gtk3/Utility/GSimpleAction.swift index d73475bfa93..e2c00e31c9c 100644 --- a/Sources/Gtk3/Utility/GSimpleAction.swift +++ b/Sources/Gtk3/Utility/GSimpleAction.swift @@ -9,38 +9,61 @@ public class GSimpleAction: GAction, GObjectRepresentable { @GObjectProperty(named: "enabled") var enabled: Bool - private class Action { - var run: () -> Void - - init(_ action: @escaping () -> Void) { - run = action - } + private class Box { + var value: T + init(_ value: T) { self.value = value } } public init(name: String, action: @escaping () -> Void) { actionPointer = g_simple_action_new(name, nil) - let wrappedAction = Action(action) + connectActionSignal(Box(action)) { _, _, data in + let action = Unmanaged Void>>.fromOpaque(data).takeUnretainedValue() + action.value() + } + } + + public init(name: String, state: Bool, action: @escaping (Bool) -> Void) { + actionPointer = g_simple_action_new_stateful( + name, + nil, + g_variant_new_boolean(state.toGBoolean()) + ) + + connectActionSignal(Box(action)) { actionPointer, _, data in + // Get the current state... + let stateVariant = g_action_get_state(OpaquePointer(actionPointer)) + let state = g_variant_get_boolean(stateVariant).toBool() + + // ...toggle it... + let newState = !state + g_simple_action_set_state( + OpaquePointer(actionPointer), + g_variant_new_boolean(newState.toGBoolean()) + ) - let handler: - @convention(c) ( - UnsafeMutableRawPointer, - OpaquePointer, - UnsafeMutableRawPointer - ) -> Void = - { _, _, data in - let action = Unmanaged.fromOpaque(data) - .takeUnretainedValue() - action.run() - } + // ...then get the action and call it with the new state. + let action = Unmanaged Void>>.fromOpaque(data).takeUnretainedValue() + action.value(newState) + } + } + private func connectActionSignal( + _ value: some AnyObject, + handler: + @convention(c) ( + UnsafeMutableRawPointer, + OpaquePointer, + UnsafeMutableRawPointer + ) -> Void + ) { g_signal_connect_data( UnsafeMutableRawPointer(actionPointer), "activate", gCallback(handler), - Unmanaged.passRetained(wrappedAction).toOpaque(), + Unmanaged.passRetained(value).toOpaque(), { data, _ in - Unmanaged.fromOpaque(data!).release() + Unmanaged.fromOpaque(data!).release() }, G_CONNECT_AFTER ) diff --git a/Sources/Gtk3Backend/Gtk3Backend.swift b/Sources/Gtk3Backend/Gtk3Backend.swift index a0e977aafb9..542476e1ea8 100644 --- a/Sources/Gtk3Backend/Gtk3Backend.swift +++ b/Sources/Gtk3Backend/Gtk3Backend.swift @@ -240,13 +240,16 @@ public final class Gtk3Backend: AppBackend { switch item { case .button(let label, let action): if let action { - actionMap.addAction(named: actionName, action: action) + actionMap.addAction(GSimpleAction(name: actionName, action: action)) } model.appendItem(label: label, actionName: "\(actionNamespace).\(actionName)") case .toggle(let label, let value, let onChange): - // FIXME: Implement - logger.warning("menu toggles not implemented") + actionMap.addAction( + GSimpleAction(name: actionName, state: value, action: onChange) + ) + + model.appendItem(label: label, actionName: "\(actionNamespace).\(actionName)") case .submenu(let submenu): model.appendSubmenu( label: submenu.label, diff --git a/Sources/GtkBackend/GtkBackend.swift b/Sources/GtkBackend/GtkBackend.swift index af4e733ce91..d074dc532b7 100644 --- a/Sources/GtkBackend/GtkBackend.swift +++ b/Sources/GtkBackend/GtkBackend.swift @@ -275,13 +275,16 @@ public final class GtkBackend: AppBackend { switch item { case .button(let label, let action): if let action { - actionMap.addAction(named: actionName, action: action) + actionMap.addAction(GSimpleAction(name: actionName, action: action)) } model.appendItem(label: label, actionName: "\(actionNamespace).\(actionName)") case .toggle(let label, let value, let onChange): - // FIXME: Implement - logger.warning("menu toggles not implemented") + actionMap.addAction( + GSimpleAction(name: actionName, state: value, action: onChange) + ) + + model.appendItem(label: label, actionName: "\(actionNamespace).\(actionName)") case .submenu(let submenu): model.appendSubmenu( label: submenu.label,