Skip to content

Commit bddf4bf

Browse files
committed
Refactor App lifecycle and event system; enhance menu/tray APIs
Replaces AppRunner with Application for managing app lifecycle, updating usage in main.swift. Moves EventEmitter and BaseEventEmitter to a separate file, adding hooks for listener management. Refactors Image to remove asset and system icon loading. Improves Menu and TrayIcon event handling with listener registration/removal, adds menu item removal APIs, and introduces Placement enum for UI positioning. Updates CNativeAPI submodule.
1 parent 63c3d48 commit bddf4bf

File tree

11 files changed

+694
-587
lines changed

11 files changed

+694
-587
lines changed

Examples/Example/main.swift

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ import NativeAPI
121121
// Configure tray icon event handlers
122122
trayIcon.onClicked { event in
123123
print("👆 托盘图标左键点击")
124-
trayIcon.openContextMenu()
124+
_ = trayIcon.openContextMenu()
125125
}
126126

127127
trayIcon.onRightClicked { event in
@@ -182,20 +182,9 @@ import NativeAPI
182182
@MainActor func imageLoadingDemo() {
183183
print("\n=== Image Loading Demo ===")
184184

185-
// Try to load a system icon
186-
if let systemIcon = Image.fromSystemIcon("NSApplicationIcon") {
187-
print("✅ Loaded system icon: \(systemIcon.size.width)x\(systemIcon.size.height)")
188-
print(" Format: \(systemIcon.format ?? "unknown")")
189-
} else {
190-
print("❌ Failed to load system icon")
191-
}
192-
193-
// Try to load from asset (if available)
194-
if let assetIcon = Image.fromAsset("assets/icons/app_icon.png") {
195-
print("✅ Loaded asset icon: \(assetIcon.size.width)x\(assetIcon.size.height)")
196-
} else {
197-
print("ℹ️ Asset icon not found (this is expected if not bundled)")
198-
}
185+
// Note: Image loading examples would go here
186+
// You can use Image.fromFile("path/to/icon.png") or Image.fromBase64("data:...")
187+
print("ℹ️ Image loading demo - use Image.fromFile() or Image.fromBase64()")
199188
}
200189

201190
// MARK: - Main Application
@@ -228,11 +217,11 @@ import NativeAPI
228217
return
229218
}
230219

231-
// Don't hide the window immediately - let AppRunner handle the visibility
220+
// Don't hide the window immediately - let Application handle the visibility
232221
// window.hide()
233222

234-
let exitCode = AppRunner.shared.run(with: window)
235-
print("💡 应用程序退出,退出码: \(exitCode.rawValue)")
223+
let exitCode = Application.shared.run(with: window)
224+
print("💡 应用程序退出,退出码: \(exitCode)")
236225
}
237226

238227
runApplication()

Sources/CNativeAPI

Submodule CNativeAPI updated 104 files

Sources/NativeAPI/AppRunner.swift

Lines changed: 0 additions & 61 deletions
This file was deleted.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import CNativeAPI
2+
import Foundation
3+
4+
/// Application manages the application lifecycle and event loop
5+
public class Application: @unchecked Sendable {
6+
/// Shared singleton instance
7+
public static let shared = Application()
8+
9+
private let nativeHandle: native_application_t
10+
11+
private init() {
12+
nativeHandle = native_application_get_instance()
13+
}
14+
15+
/// Run the application main event loop
16+
/// This method starts the main event loop and blocks until the application exits
17+
/// - Returns: Exit code of the application (0 for success)
18+
public func run() -> Int32 {
19+
return Int32(native_application_run(nativeHandle))
20+
}
21+
22+
/// Run the application with the specified window
23+
/// - Parameter window: The window to run the application with
24+
/// - Returns: Exit code of the application (0 for success)
25+
public func run(with window: Window) -> Int32 {
26+
return Int32(native_application_run_with_window(nativeHandle, window.handle))
27+
}
28+
29+
/// Request the application to quit
30+
/// - Parameter exitCode: The exit code to use when quitting (default: 0)
31+
public func quit(exitCode: Int32 = 0) {
32+
native_application_quit(nativeHandle, exitCode)
33+
}
34+
35+
/// Check if the application is currently running
36+
/// - Returns: true if the app is running, false otherwise
37+
public var isRunning: Bool {
38+
return native_application_is_running(nativeHandle)
39+
}
40+
41+
/// Check if this is a single instance application
42+
/// - Returns: true if only one instance is allowed, false otherwise
43+
public var isSingleInstance: Bool {
44+
return native_application_is_single_instance(nativeHandle)
45+
}
46+
47+
/// Set the application icon
48+
/// - Parameter iconPath: Path to the icon file
49+
/// - Returns: true if the icon was set successfully, false otherwise
50+
public func setIcon(_ iconPath: String) -> Bool {
51+
return native_application_set_icon(nativeHandle, iconPath)
52+
}
53+
54+
/// Show or hide the dock icon (macOS only)
55+
/// - Parameter visible: true to show the dock icon, false to hide it
56+
/// - Returns: true if the operation succeeded, false otherwise
57+
public func setDockIconVisible(_ visible: Bool) -> Bool {
58+
return native_application_set_dock_icon_visible(nativeHandle, visible)
59+
}
60+
}
61+
62+
/// Convenience function to run the application with the specified window
63+
/// This is equivalent to calling Application.shared.run(with: window)
64+
/// - Parameter window: The window to run the application with
65+
/// - Returns: Exit code of the application (0 for success)
66+
public func runApp(with window: Window) -> Int32 {
67+
return Application.shared.run(with: window)
68+
}
69+
70+
/// Convenience function to run the application without a window
71+
/// - Returns: Exit code of the application (0 for success)
72+
public func runApp() -> Int32 {
73+
return Application.shared.run()
74+
}
75+

Sources/NativeAPI/Foundation/Event.swift

Lines changed: 0 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -24,175 +24,3 @@ public struct CallbackEventListener<EventType: Event>: EventListener {
2424
callback(event)
2525
}
2626
}
27-
28-
/// Event emitter protocol that provides event emission capabilities
29-
public protocol EventEmitter: AnyObject {
30-
/// Add a typed event listener for a specific event type
31-
/// - Parameter listener: The event listener
32-
/// - Returns: Unique listener ID that can be used to remove the listener
33-
func addListener<T: Event>(_ listener: CallbackEventListener<T>) -> Int
34-
35-
/// Add a callback function as a listener for a specific event type
36-
/// - Parameter callback: The callback function
37-
/// - Returns: Unique listener ID that can be used to remove the listener
38-
func addCallbackListener<T: Event>(_ callback: @escaping (T) -> Void) -> Int
39-
40-
/// Remove a listener by its ID
41-
/// - Parameter listenerId: The listener ID
42-
/// - Returns: true if the listener was found and removed, false otherwise
43-
func removeListener(_ listenerId: Int) -> Bool
44-
45-
/// Remove all listeners for a specific event type, or all listeners if no type is specified
46-
func removeAllListeners<T: Event>(_ eventType: T.Type)
47-
func removeAllListeners()
48-
49-
/// Get the number of listeners registered for a specific event type
50-
func getListenerCount<T: Event>(_ eventType: T.Type) -> Int
51-
52-
/// Get the total number of registered listeners across all event types
53-
var totalListenerCount: Int { get }
54-
55-
/// Check if there are any listeners for a specific event type
56-
func hasListeners<T: Event>(_ eventType: T.Type) -> Bool
57-
58-
/// Emit an event synchronously to all registered listeners
59-
func emitSync<T: Event>(_ event: T)
60-
61-
/// Emit an event synchronously using a factory function
62-
func emitSyncWithFactory<T: Event>(_ eventFactory: () -> T)
63-
64-
/// Emit an event asynchronously
65-
func emitAsync<T: Event>(_ event: T)
66-
67-
/// Emit an event asynchronously using a factory function
68-
func emitAsyncWithFactory<T: Event>(_ eventFactory: @escaping () -> T)
69-
70-
/// Dispose of the event emitter and clean up resources
71-
func disposeEventEmitter()
72-
}
73-
74-
/// Default implementation of EventEmitter
75-
open class BaseEventEmitter: EventEmitter {
76-
/// Map of event types to their listeners
77-
private var listeners: [String: [Int: any EventListener]] = [:]
78-
79-
/// Counter for generating unique listener IDs
80-
private var nextListenerId: Int = 0
81-
82-
/// Queue for asynchronous event emission
83-
private let eventQueue = DispatchQueue(label: "EventEmitter.queue", qos: .userInitiated)
84-
85-
public init() {}
86-
87-
public func addListener<T: Event>(_ listener: CallbackEventListener<T>) -> Int {
88-
let eventType = T.eventType
89-
let listenerId = nextListenerId
90-
nextListenerId += 1
91-
92-
if listeners[eventType] == nil {
93-
listeners[eventType] = [:]
94-
}
95-
listeners[eventType]![listenerId] = listener
96-
97-
return listenerId
98-
}
99-
100-
public func addCallbackListener<T: Event>(_ callback: @escaping (T) -> Void) -> Int {
101-
let listener = CallbackEventListener<T>(callback)
102-
return addListener(listener)
103-
}
104-
105-
public func removeListener(_ listenerId: Int) -> Bool {
106-
for (eventType, eventListeners) in listeners {
107-
var mutableListeners = eventListeners
108-
if mutableListeners.removeValue(forKey: listenerId) != nil {
109-
if mutableListeners.isEmpty {
110-
listeners.removeValue(forKey: eventType)
111-
} else {
112-
listeners[eventType] = mutableListeners
113-
}
114-
return true
115-
}
116-
}
117-
return false
118-
}
119-
120-
public func removeAllListeners<T: Event>(_ eventType: T.Type) {
121-
listeners.removeValue(forKey: T.eventType)
122-
}
123-
124-
public func removeAllListeners() {
125-
listeners.removeAll()
126-
}
127-
128-
public func getListenerCount<T: Event>(_ eventType: T.Type) -> Int {
129-
return listeners[T.eventType]?.count ?? 0
130-
}
131-
132-
public var totalListenerCount: Int {
133-
return listeners.values.reduce(0) { $0 + $1.count }
134-
}
135-
136-
public func hasListeners<T: Event>(_ eventType: T.Type) -> Bool {
137-
return getListenerCount(eventType) > 0
138-
}
139-
140-
public func emitSync<T: Event>(_ event: T) {
141-
let eventType = T.eventType
142-
guard let eventListeners = listeners[eventType] else { return }
143-
144-
// Create a copy of the listeners list to avoid concurrent modification
145-
let listenersCopy = Array(eventListeners.values)
146-
147-
for listener in listenersCopy {
148-
if let callbackListener = listener as? CallbackEventListener<T> {
149-
callbackListener.onEvent(event)
150-
}
151-
}
152-
}
153-
154-
public func emitSyncWithFactory<T: Event>(_ eventFactory: () -> T) {
155-
let event = eventFactory()
156-
emitSync(event)
157-
}
158-
159-
public func emitAsync<T: Event>(_ event: T) {
160-
eventQueue.async { [weak self] in
161-
self?.emitSync(event)
162-
}
163-
}
164-
165-
public func emitAsyncWithFactory<T: Event>(_ eventFactory: @escaping () -> T) {
166-
eventQueue.async { [weak self] in
167-
let event = eventFactory()
168-
self?.emitSync(event)
169-
}
170-
}
171-
172-
public func disposeEventEmitter() {
173-
listeners.removeAll()
174-
}
175-
}
176-
177-
/// Extension methods for easier event listener registration
178-
public extension EventEmitter {
179-
/// Convenience method to add a callback listener using a function
180-
func on<T: Event>(_ callback: @escaping (T) -> Void) -> Int {
181-
return addCallbackListener(callback)
182-
}
183-
184-
/// Convenience method to add a one-time listener that removes itself after firing
185-
func once<T: Event>(_ callback: @escaping (T) -> Void) -> Int {
186-
var listenerId: Int = 0
187-
listenerId = addCallbackListener { [weak self] event in
188-
self?.removeListener(listenerId)
189-
callback(event)
190-
}
191-
return listenerId
192-
}
193-
194-
/// Remove a listener (alias for removeListener)
195-
func off(_ listenerId: Int) -> Bool {
196-
return removeListener(listenerId)
197-
}
198-
}

0 commit comments

Comments
 (0)