Skip to content

Commit 828511c

Browse files
egorzhdancompnerd
authored andcommitted
Menus and Shortcuts: make _MenuBuilder materialize the menus
This change teaches `_MenuBuilder` to build the window menus. It handles submenus & icons similarly to `Win32Menu`.
1 parent 4d7f528 commit 828511c

File tree

2 files changed

+80
-4
lines changed

2 files changed

+80
-4
lines changed

Sources/SwiftWin32/Platform/Win32+Menu.swift

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal final class _MenuBuilder: MenuSystem {
1313
private var menus: OrderedSet<Menu>
1414

1515
internal init?(for view: View) {
16-
self.hMenu = MenuHandle(owning: CreateMenu())
16+
self.hMenu = MenuHandle(owning: view is Window ? CreateMenu() : CreatePopupMenu())
1717
if self.hMenu.value == nil {
1818
log.warning("CreateMenu: \(Error(win32: GetLastError()))")
1919
return nil
@@ -22,8 +22,42 @@ internal final class _MenuBuilder: MenuSystem {
2222
self.menus = []
2323
}
2424

25+
private func append<T: Collection>(_ menus: T, to hMenu: MenuHandle)
26+
where T.Element: MenuElement {
27+
for (index, child) in menus.enumerated() {
28+
let hImage: BitmapHandle? =
29+
BitmapHandle(from: child.image?.bitmap)
30+
31+
var hSubmenu: MenuHandle?
32+
if let submenu = child as? Menu {
33+
hSubmenu = MenuHandle(referencing: CreatePopupMenu())
34+
if hSubmenu?.value == nil {
35+
log.warning("CreateMenu: \(Error(win32: GetLastError()))")
36+
continue
37+
}
38+
append(submenu.children, to: hSubmenu!)
39+
}
40+
41+
var wide = child.title.wide
42+
var info = wide.withUnsafeMutableBufferPointer {
43+
MENUITEMINFOW(cbSize: UINT(MemoryLayout<MENUITEMINFOW>.size),
44+
fMask: UINT(MIIM_FTYPE | MIIM_STATE | MIIM_ID | MIIM_STRING | MIIM_SUBMENU | MIIM_DATA | MIIM_BITMAP),
45+
fType: UINT(MFT_STRING), fState: UINT(MFS_ENABLED), wID: UInt32.random(in: .min ... .max),
46+
hSubMenu: hSubmenu?.value, hbmpChecked: nil, hbmpUnchecked: nil, dwItemData: 0,
47+
dwTypeData: $0.baseAddress, cch: UINT(child.title.count), hbmpItem: hImage?.value)
48+
}
49+
InsertMenuItemW(hMenu.value, UINT(index), true, &info)
50+
}
51+
}
52+
2553
override func setNeedsRebuild() {
26-
// TODO(compnerd) create the actual menus
54+
// Remove the old submenus from the menu:
55+
for index in (0..<GetMenuItemCount(self.hMenu.value)).reversed() {
56+
DeleteMenu(self.hMenu.value, UINT(index), UINT(MF_BYPOSITION))
57+
}
58+
59+
// Create the new submenus and add them to the root menu:
60+
append(self.menus, to: self.hMenu)
2761
}
2862
}
2963

Tests/UICoreTests/MenuBuilderTests.swift

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: BSD-3-Clause
33

44
import XCTest
5+
import WinSDK
56
@testable import SwiftWin32
67

78
final class MenuBuilderTests: XCTestCase {
@@ -85,8 +86,47 @@ final class MenuBuilderTests: XCTestCase {
8586

8687
func testBuildMenu() {
8788
let view: View = View(frame: .zero)
88-
let builder = _MenuBuilder(for: view)
89-
XCTAssertNotNil((builder?.system as? _MenuBuilder)?.hMenu.value)
89+
guard let builder = _MenuBuilder(for: view) else { return }
90+
91+
let hMenu = (builder.system as? _MenuBuilder)?.hMenu
92+
XCTAssertNotNil(hMenu?.value)
93+
XCTAssertEqual(GetMenuItemCount(hMenu?.value), 0)
94+
}
95+
96+
func testBuildMenuWithChildren() {
97+
let view: View = View(frame: .zero)
98+
guard let builder = _MenuBuilder(for: view) else { return }
99+
100+
let menu = Menu(title: "menu1", children: [Command(title: "item1") { _ in },
101+
Action(title: "item2") { _ in }])
102+
builder.insertSibling(menu, afterMenu: .root)
103+
builder.setNeedsRebuild()
104+
105+
let hRootMenu = (builder.system as? _MenuBuilder)?.hMenu
106+
XCTAssertNotNil(hRootMenu?.value)
107+
XCTAssertEqual(GetMenuItemCount(hRootMenu?.value), 1)
108+
109+
let hMenu = GetSubMenu(hRootMenu?.value, 0)
110+
XCTAssertNotNil(hMenu)
111+
XCTAssertEqual(GetMenuItemCount(hMenu), 2)
112+
}
113+
114+
func testRebuildMenu() {
115+
let view: View = View(frame: .zero)
116+
guard let builder = _MenuBuilder(for: view) else { return }
117+
118+
let hMenu1 = (builder.system as? _MenuBuilder)?.hMenu
119+
120+
let menu = Menu(title: "title", children: [Command(title: "cmd") { _ in }])
121+
builder.insertSibling(menu, afterMenu: .root)
122+
builder.setNeedsRebuild()
123+
124+
let hMenu2 = (builder.system as? _MenuBuilder)?.hMenu
125+
126+
XCTAssert(hMenu1 === hMenu2)
127+
XCTAssertNotNil(hMenu1)
128+
XCTAssertNotNil(hMenu2)
129+
XCTAssertEqual(hMenu1?.value, hMenu2?.value)
90130
}
91131

92132
public static var allTests = [
@@ -96,5 +136,7 @@ final class MenuBuilderTests: XCTestCase {
96136
("testRemove", testRemove),
97137
("testReplace", testReplace),
98138
("testBuildMenu", testBuildMenu),
139+
("testBuildMenuWithChildren", testBuildMenuWithChildren),
140+
("testRebuildMenu", testRebuildMenu),
99141
]
100142
}

0 commit comments

Comments
 (0)