Skip to content

Commit e34c621

Browse files
committed
Add display and image APIs, refactor menu events
Introduces Display and DisplayManager classes for monitor enumeration and information, and adds a cross-platform Image class for image handling. Refactors Menu and MenuItem to use event-based callbacks and BaseEventEmitter, updates Example to demonstrate new display and image APIs, and synchronizes CNativeAPI submodule.
1 parent 5dbdbe1 commit e34c621

File tree

14 files changed

+1300
-1079
lines changed

14 files changed

+1300
-1079
lines changed

Examples/Example/main.swift

Lines changed: 88 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,23 @@ import NativeAPI
77
let trayManager = TrayManager.shared
88

99
// Check if system tray is supported
10-
guard trayManager.isSupported() else {
10+
guard trayManager.isSupported else {
1111
print("❌ System tray is not supported on this platform")
1212
return
1313
}
1414

1515
// Create a basic tray icon
16-
guard let trayIcon = trayManager.create() else {
17-
print("❌ Failed to create tray icon")
18-
return
19-
}
20-
21-
trayIcon.setTitle("NativeAPI Demo")
22-
trayIcon.setTooltip("NativeAPI Tray Icon Demo")
16+
let trayIcon = TrayIcon()
17+
trayIcon.title = "NativeAPI Demo"
18+
trayIcon.tooltip = "NativeAPI Tray Icon Demo"
2319

2420
// Create context menu for tray icon
2521
let contextMenu = Menu()
2622

2723
// Add "Show Window" menu item
2824
let showItem = MenuItem("显示窗口")
2925
contextMenu.addItem(showItem)
30-
showItem.onClicked { menuItem in
26+
showItem.onClicked { event in
3127
print("📱 显示窗口")
3228
}
3329

@@ -37,14 +33,14 @@ import NativeAPI
3733
// Add "About" menu item
3834
let aboutItem = MenuItem("关于")
3935
contextMenu.addItem(aboutItem)
40-
aboutItem.onClicked { menuItem in
36+
aboutItem.onClicked { event in
4137
print("ℹ️ 关于 - NativeAPI Demo v1.0")
4238
}
4339

4440
// Add "Settings" menu item
4541
let settingsItem = MenuItem("设置")
4642
contextMenu.addItem(settingsItem)
47-
settingsItem.onClicked { menuItem in
43+
settingsItem.onClicked { event in
4844
print("⚙️ 打开设置面板")
4945
}
5046

@@ -53,56 +49,43 @@ import NativeAPI
5349

5450
// Add checkbox items for demonstration
5551
let showToolbarItem = MenuItem("显示工具栏", type: .checkbox)
56-
showToolbarItem.setChecked(true)
5752
contextMenu.addItem(showToolbarItem)
5853

5954
let autoSaveItem = MenuItem("自动保存", type: .checkbox)
60-
autoSaveItem.setChecked(false)
6155
contextMenu.addItem(autoSaveItem)
6256

6357
// Add event handlers for checkboxes
64-
showToolbarItem.onClicked { menuItem in
65-
let isChecked = menuItem.isChecked()
66-
print("☑️ 工具栏\(isChecked ? "显示" : "隐藏")")
58+
showToolbarItem.onClicked { event in
59+
print("☑️ 工具栏切换")
6760
}
6861

69-
autoSaveItem.onClicked { menuItem in
70-
let isChecked = menuItem.isChecked()
71-
print("☑️ 自动保存\(isChecked ? "已启用" : "已禁用")")
62+
autoSaveItem.onClicked { event in
63+
print("☑️ 自动保存切换")
7264
}
7365

7466
// Add separator
7567
contextMenu.addSeparator()
7668

7769
// Add radio button group for view mode selection
7870
let compactViewItem = MenuItem("紧凑视图", type: .radio)
79-
compactViewItem.setRadioGroup(1)
80-
compactViewItem.setChecked(false)
8171
contextMenu.addItem(compactViewItem)
8272

8373
let normalViewItem = MenuItem("普通视图", type: .radio)
84-
normalViewItem.setRadioGroup(1)
85-
normalViewItem.setChecked(true)
8674
contextMenu.addItem(normalViewItem)
8775

8876
let detailedViewItem = MenuItem("详细视图", type: .radio)
89-
detailedViewItem.setRadioGroup(1)
90-
detailedViewItem.setChecked(false)
9177
contextMenu.addItem(detailedViewItem)
9278

9379
// Add event handlers for radio buttons
94-
compactViewItem.onClicked { menuItem in
95-
menuItem.setState(.checked)
80+
compactViewItem.onClicked { event in
9681
print("🔘 视图模式: 紧凑视图")
9782
}
9883

99-
normalViewItem.onClicked { menuItem in
100-
menuItem.setState(.checked)
84+
normalViewItem.onClicked { event in
10185
print("🔘 视图模式: 普通视图")
10286
}
10387

104-
detailedViewItem.onClicked { menuItem in
105-
menuItem.setState(.checked)
88+
detailedViewItem.onClicked { event in
10689
print("🔘 视图模式: 详细视图")
10790
}
10891

@@ -112,42 +95,97 @@ import NativeAPI
11295
// Add "Exit" menu item
11396
let exitItem = MenuItem("退出")
11497
contextMenu.addItem(exitItem)
115-
exitItem.onClicked { menuItem in
98+
exitItem.onClicked { event in
11699
print("👋 退出应用程序")
117100
exit(0)
118101
}
119102

120103
// Set the context menu for tray icon
121-
trayIcon.setContextMenu(contextMenu)
104+
trayIcon.contextMenu = contextMenu
122105

123-
// // Configure click handlers
124-
// trayIcon.onLeftClick { trayIcon, event in
125-
// print("👆 托盘图标左键点击")
126-
// }
106+
// Configure click handlers
107+
trayIcon.onClicked { event in
108+
print("👆 托盘图标左键点击")
109+
}
127110

128-
// trayIcon.onRightClick { trayIcon, event in
129-
// print("👆 托盘图标右键点击")
130-
// }
111+
trayIcon.onRightClicked { event in
112+
print("👆 托盘图标右键点击")
113+
}
131114

132-
// trayIcon.onDoubleClick { trayIcon, event in
133-
// print("👆 托盘图标双击")
134-
// }
115+
trayIcon.onDoubleClicked { event in
116+
print("👆 托盘图标双击")
117+
}
135118

136119
// Show the tray icon
137-
if trayIcon.show() {
138-
print("✅ 托盘图标已显示,右键点击查看菜单")
120+
trayIcon.isVisible = true
121+
print("✅ 托盘图标已显示,右键点击查看菜单")
122+
}
123+
124+
/// Display information demo
125+
@MainActor func displayInfoDemo() {
126+
print("\n=== Display Information Demo ===")
127+
let displayManager = DisplayManager.shared
128+
129+
// Get all displays
130+
let displays = displayManager.getAll()
131+
print("📺 Found \(displays.count) display(s)")
132+
133+
for (index, display) in displays.enumerated() {
134+
print("\nDisplay \(index + 1):")
135+
print(" ID: \(display.id)")
136+
print(" Name: \(display.name)")
137+
print(" Size: \(display.size.width)x\(display.size.height)")
138+
print(" Position: (\(display.position.x), \(display.position.y))")
139+
print(" Scale Factor: \(display.scaleFactor)")
140+
print(" Primary: \(display.isPrimary ? "Yes" : "No")")
141+
print(" Orientation: \(display.orientation)")
142+
print(" Refresh Rate: \(display.refreshRate) Hz")
143+
print(" Bit Depth: \(display.bitDepth)")
144+
}
145+
146+
// Get primary display
147+
if let primaryDisplay = displayManager.getPrimary() {
148+
print("\n🖥️ Primary Display: \(primaryDisplay.name)")
149+
}
150+
151+
// Get cursor position
152+
let cursorPos = displayManager.getCursorPosition()
153+
print("🖱️ Cursor Position: (\(cursorPos.x), \(cursorPos.y))")
154+
}
155+
156+
/// Image loading demo
157+
@MainActor func imageLoadingDemo() {
158+
print("\n=== Image Loading Demo ===")
159+
160+
// Try to load a system icon
161+
if let systemIcon = Image.fromSystemIcon("NSApplicationIcon") {
162+
print("✅ Loaded system icon: \(systemIcon.size.width)x\(systemIcon.size.height)")
163+
print(" Format: \(systemIcon.format ?? "unknown")")
164+
} else {
165+
print("❌ Failed to load system icon")
166+
}
167+
168+
// Try to load from asset (if available)
169+
if let assetIcon = Image.fromAsset("assets/icons/app_icon.png") {
170+
print("✅ Loaded asset icon: \(assetIcon.size.width)x\(assetIcon.size.height)")
139171
} else {
140-
print("❌ Failed to show tray icon")
172+
print("ℹ️ Asset icon not found (this is expected if not bundled)")
141173
}
142174
}
143175

144176
// MARK: - Main Application
145177

146178
@MainActor func runApplication() {
179+
// Display information demo
180+
displayInfoDemo()
181+
182+
// Image loading demo
183+
imageLoadingDemo()
184+
147185
// Create tray icon with context menu
148186
createTrayIconWithContextMenu()
149187

150-
print("\n✅ NativeAPI 托盘图标演示已启动")
188+
print("\n✅ NativeAPI 演示已启动")
151189
print("💡 功能测试:")
152190
print(" • 普通菜单项: 显示窗口、关于、设置")
153191
print(" • 复选框菜单项: 显示工具栏、自动保存")
@@ -164,11 +202,11 @@ import NativeAPI
164202
return
165203
}
166204

167-
// Hide the window so only tray icon is visible
168-
window.hide()
205+
// Don't hide the window immediately - let AppRunner handle the visibility
206+
// window.hide()
169207

170208
let exitCode = AppRunner.shared.run(with: window)
171209
print("💡 应用程序退出,退出码: \(exitCode.rawValue)")
172210
}
173211

174-
runApplication()
212+
runApplication()

Sources/CNativeAPI

Submodule CNativeAPI updated 99 files

Sources/NativeAPI/Display.swift

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import CNativeAPI
2+
import Foundation
3+
4+
/// Display orientation enumeration
5+
public enum DisplayOrientation: Int, CaseIterable {
6+
case portrait = 0
7+
case landscape = 90
8+
case portraitFlipped = 180
9+
case landscapeFlipped = 270
10+
11+
public init(value: Int) {
12+
switch value {
13+
case 0: self = .portrait
14+
case 90: self = .landscape
15+
case 180: self = .portraitFlipped
16+
case 270: self = .landscapeFlipped
17+
default: self = .portrait
18+
}
19+
}
20+
}
21+
22+
/// Display represents a single display/monitor
23+
public class Display: BaseNativeHandleWrapper<native_display_t> {
24+
25+
public init(nativeHandle: native_display_t) {
26+
super.init(nativeHandle: nativeHandle) { native_display_free($0) }
27+
}
28+
29+
/// Unique identifier for this display
30+
public var id: String {
31+
guard let idPtr = native_display_get_id(nativeHandle) else {
32+
return ""
33+
}
34+
let id = String(cString: idPtr)
35+
free_c_str(idPtr)
36+
return id
37+
}
38+
39+
/// Human-readable name of this display
40+
public var name: String {
41+
guard let namePtr = native_display_get_name(nativeHandle) else {
42+
return ""
43+
}
44+
let name = String(cString: namePtr)
45+
free_c_str(namePtr)
46+
return name
47+
}
48+
49+
/// Position of this display in the virtual desktop
50+
public var position: Point {
51+
let nativePoint = native_display_get_position(nativeHandle)
52+
return Point(nativePoint)
53+
}
54+
55+
/// Physical size of this display in pixels
56+
public var size: Size {
57+
let nativeSize = native_display_get_size(nativeHandle)
58+
return Size(nativeSize)
59+
}
60+
61+
/// Work area of this display (excluding taskbar/dock)
62+
public var workArea: Rect {
63+
let nativeRect = native_display_get_work_area(nativeHandle)
64+
return Rect(nativeRect)
65+
}
66+
67+
/// Scale factor of this display
68+
public var scaleFactor: Double {
69+
return native_display_get_scale_factor(nativeHandle)
70+
}
71+
72+
/// Whether this is the primary display
73+
public var isPrimary: Bool {
74+
return native_display_is_primary(nativeHandle)
75+
}
76+
77+
/// Orientation of this display
78+
public var orientation: DisplayOrientation {
79+
let nativeOrientation = native_display_get_orientation(nativeHandle)
80+
return DisplayOrientation(value: Int(nativeOrientation.rawValue))
81+
}
82+
83+
/// Refresh rate of this display in Hz
84+
public var refreshRate: Int {
85+
return Int(native_display_get_refresh_rate(nativeHandle))
86+
}
87+
88+
/// Bit depth of this display
89+
public var bitDepth: Int {
90+
return Int(native_display_get_bit_depth(nativeHandle))
91+
}
92+
93+
/// Native platform-specific object handle
94+
public var nativeObject: UnsafeMutableRawPointer? {
95+
return native_display_get_native_object(nativeHandle)
96+
}
97+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import CNativeAPI
2+
import Foundation
3+
4+
/// DisplayManager manages all displays/monitors
5+
public class DisplayManager: @unchecked Sendable {
6+
/// Singleton instance of DisplayManager
7+
public static let shared = DisplayManager()
8+
9+
private init() {}
10+
11+
/// Returns a list of all displays.
12+
///
13+
/// This method retrieves a list of all available displays using the native
14+
/// display manager API. It then converts each display handle into a Swift
15+
/// Display object and returns the list.
16+
public func getAll() -> [Display] {
17+
let displayList = native_display_manager_get_all()
18+
var displays: [Display] = []
19+
20+
for i in 0..<displayList.count {
21+
if let nativeHandle = (displayList.displays + i).pointee {
22+
displays.append(Display(nativeHandle: nativeHandle))
23+
}
24+
}
25+
26+
// Note: In the Dart version, the display list is not freed
27+
// native_display_list_free(displayList)
28+
29+
return displays
30+
}
31+
32+
/// Returns the current cursor position.
33+
///
34+
/// This method retrieves the current cursor position using the native display
35+
/// manager API. It then converts the position into a Swift Point object and
36+
/// returns it.
37+
public func getCursorPosition() -> Point {
38+
let nativePoint = native_display_manager_get_cursor_position()
39+
return Point(nativePoint)
40+
}
41+
42+
/// Returns the primary display.
43+
///
44+
/// This method retrieves the primary display using the native display manager
45+
/// API. It then converts the display handle into a Swift Display object and
46+
/// returns it.
47+
public func getPrimary() -> Display? {
48+
guard let nativeHandle = native_display_manager_get_primary() else {
49+
return nil
50+
}
51+
return Display(nativeHandle: nativeHandle)
52+
}
53+
}

0 commit comments

Comments
 (0)