Skip to content

Commit

Permalink
UI: support showing basic context menus
Browse files Browse the repository at this point in the history
  • Loading branch information
egorzhdan authored and compnerd committed Dec 8, 2020
1 parent 2a1acfb commit 842d5c3
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 2 deletions.
3 changes: 3 additions & 0 deletions Sources/SwiftWin32/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ target_sources(SwiftWin32 PRIVATE
UI/ContentSizeCategoryAdjusting.swift
UI/ContentSizeCategoryImageAdjusting.swift
UI/ContextMenuConfiguration.swift
UI/ContextMenuInteraction.swift
UI/ContextMenuInteractionAnimating.swift
UI/ContextMenuInteractionCommitAnimating.swift
UI/ContextMenuInteractionDelegate.swift
UI/Control.swift
UI/CoordinateSpace.swift
UI/DatePicker.swift
Expand Down
11 changes: 11 additions & 0 deletions Sources/SwiftWin32/Support/WindowsHandle+UIExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,14 @@ extension HBITMAP__: HandleValue {
}
}
internal typealias BitmapHandle = ManagedHandle<HBITMAP__>

extension HMENU: HandleValue {
typealias HandleType = HMENU
internal static func release(_ hMenu: HandleType?) {
if let hMenu = hMenu {
DestroyMenu(hMenu)
}
}
}

internal typealias MenuHandle = ManagedHandle<HMENU>
4 changes: 4 additions & 0 deletions Sources/SwiftWin32/UI/ContextMenuConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ public class ContextMenuConfiguration {

private var previewProvider: ContextMenuContentPreviewProvider
private var actionProvider: ContextMenuActionProvider?

internal func provideActions() -> Menu? {
actionProvider?([]) // TODO suggest default actions
}
}
10 changes: 9 additions & 1 deletion Sources/SwiftWin32/UI/ContextMenuInteraction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ extension ContextMenuInteraction {
}
}

public class ContextMenuInteraction {
public class ContextMenuInteraction: Interaction {
// MARK - Creating a Context Menu Interaction Object

/// Creates a context menu interaction object with the specified delegate
Expand All @@ -34,6 +34,14 @@ public class ContextMenuInteraction {
/// and responds to interaction-related events.
public private(set) weak var delegate: ContextMenuInteractionDelegate?

public private(set) var view: View? = nil

public func willMove(to view: View?) { }

public func didMove(to view: View?) {
self.view = view
}

// MARK - Getting the Interaction's Location

/// Returns the location of the user interaction in the specified view's
Expand Down
20 changes: 20 additions & 0 deletions Sources/SwiftWin32/UI/Menu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* SPDX-License-Identifier: BSD-3-Clause
**/

import WinSDK

extension Menu {
/// Constants for identifying an application's standard menus.
public struct Identifier: Equatable, Hashable, RawRepresentable {
Expand Down Expand Up @@ -275,12 +277,30 @@ extension Menu.Options {
/// A container for grouping related menu elements in an application menu or
/// contextual menu.
public class Menu: MenuElement {
internal let children: [MenuElement]

/// Creating a Menu Object

/// Creates a new menu with the specified values.
public init(title: String = "", image: Image? = nil,
identifier: Menu.Identifier? = nil, options: Menu.Options = [],
children: [MenuElement] = []) {
self.children = children
super.init(title: title, image: image)
}
}

internal struct Win32Menu {
internal let hMenu: MenuHandle
private let children: [Win32MenuElement]

internal init(_ hMenu: MenuHandle, children: [MenuElement]) {
self.hMenu = hMenu
self.children = children.map { Win32MenuElement.of($0) }
for (index, child) in self.children.enumerated() {
InsertMenuItemW(hMenu.value,
UINT(index), true,
&(child.info))
}
}
}
48 changes: 48 additions & 0 deletions Sources/SwiftWin32/UI/MenuElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* SPDX-License-Identifier: BSD-3-Clause
**/

import WinSDK

extension MenuElement {
/// Attributes that determine the style of the menu element.
public struct Attributes: OptionSet {
Expand Down Expand Up @@ -67,3 +69,49 @@ public class MenuElement {
self.image = image
}
}

internal class Win32MenuElement {
internal var info: MENUITEMINFOW
private var titleW: [WCHAR]
private let subMenu: Win32Menu?

private init(title: String, subMenu: Win32Menu?, fType: Int32) {
self.titleW = title.LPCWSTR
let titleSize = titleW.count

self.subMenu = subMenu
self.info = titleW.withUnsafeMutableBufferPointer {
MENUITEMINFOW(cbSize: UINT(MemoryLayout<MENUITEMINFOW>.size),
fMask: UINT(MIIM_FTYPE | MIIM_STATE | MIIM_ID | MIIM_STRING | MIIM_SUBMENU | MIIM_DATA),
fType: UINT(fType),
fState: UINT(MFS_ENABLED),
wID: UInt32.random(in: .min ... .max),
hSubMenu: subMenu?.hMenu.value,
hbmpChecked: nil,
hbmpUnchecked: nil,
dwItemData: 0,
dwTypeData: $0.baseAddress,
cch: UINT(titleSize),
hbmpItem: nil)
}
}

internal static func of(_ element: MenuElement) -> Win32MenuElement {
let subMenu: Win32Menu?
if let menu = element as? Menu {
subMenu = Win32Menu(MenuHandle(owning: CreatePopupMenu()),
children: menu.children)
} else {
subMenu = nil
}
return Win32MenuElement(title: element.title, subMenu: subMenu, fType: MFT_STRING)
}

internal static var separator: Win32MenuElement {
Win32MenuElement(title: "", subMenu: nil, fType: MFT_SEPARATOR)
}

internal var isSeparator: Bool {
info.fType == MFT_SEPARATOR
}
}
31 changes: 30 additions & 1 deletion Sources/SwiftWin32/UI/View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,27 @@ private let SwiftViewProc: SUBCLASSPROC = { (hWnd, uMsg, wParam, lParam, uIdSubc
let view: View? = unsafeBitCast(dwRefData, to: AnyObject.self) as? View
switch uMsg {
case UINT(WM_CONTEXTMENU):
// TODO handle popup menu events
guard let view = view,
let menuInteraction = view.interactions.first(where: { $0 is ContextMenuInteraction })
as? ContextMenuInteraction else { break }
let x = Int16(truncatingIfNeeded: lParam)
let y = Int16(truncatingIfNeeded: lParam >> 16)
let point = Point(x: Int(x), y: Int(y))
let menuConfiguration = menuInteraction.delegate?.contextMenuInteraction(menuInteraction,
configurationForMenuAtLocation: point)
if let menu = menuConfiguration?.provideActions() {
view.win32ContextMenu = Win32Menu(MenuHandle(owning: CreatePopupMenu()),
children: menu.children)
} else {
view.win32ContextMenu = nil
}
let hMenu = view.win32ContextMenu?.hMenu.value
TrackPopupMenu(hMenu, UINT(TPM_RIGHTBUTTON),
Int32(x), Int32(y), 0, view.hWnd, nil)
return 0
case UINT(WM_COMMAND):
// TODO handle menu actions
break
default:
break
}
Expand Down Expand Up @@ -202,6 +221,16 @@ public class View: Responder {
set { _ = EnableWindow(self.hWnd, newValue) }
}

public private(set) var interactions: [Interaction] = []
internal var win32ContextMenu: Win32Menu? = nil

public func addInteraction(_ interaction: Interaction) {
interaction.willMove(to: self)
interaction.view?.interactions.removeAll(where: { $0 === interaction })
interactions.append(interaction)
interaction.didMove(to: self)
}

// MARK - Managing the View Hierarchy

public private(set) var superview: View?
Expand Down

0 comments on commit 842d5c3

Please sign in to comment.