-
Notifications
You must be signed in to change notification settings - Fork 73
/
Copy pathTableView.swift
183 lines (143 loc) · 6.02 KB
/
TableView.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Copyright © 2020 Saleem Abdulrasool <[email protected]>
// SPDX-License-Identifier: BSD-3-Clause
import WinSDK
import struct Foundation.IndexPath
// Notification Proxy
// When the Window is created, the initial parent is cached. This cache cannot
// be updated. Instead, we always parent any table view control to the
// `Swift.TableView.Proxy` which is process-wide. All notifications
// about the control events will be dispatched by the proxy.
private let SwiftTableViewProxyWindowProc: WNDPROC = { (hWnd, uMsg, wParam, lParam) in
switch uMsg {
case UINT(WM_DRAWITEM):
let lpDrawItem: UnsafeMutablePointer<DRAWITEMSTRUCT> =
UnsafeMutablePointer<DRAWITEMSTRUCT>(bitPattern: UInt(lParam))!
switch lpDrawItem.pointee.itemAction {
case UINT(ODA_SELECT):
// TODO(compnerd) figure out how to render the selection
fallthrough
case UINT(ODA_DRAWENTIRE):
if let view = unsafeBitCast(lpDrawItem.pointee.itemData,
to: AnyObject.self) as? View {
let rctRect: RECT = lpDrawItem.pointee.rcItem
_ = SetWindowPos(view.hWnd, nil, CInt(rctRect.left), CInt(rctRect.top),
0, 0, UINT(SWP_NOSIZE))
// Setting `isHidden` is necessary for TableCells generated after
// initial call to `Window.makeKeyAndVisible()`
let hWndParent = GetParent(view.hWnd)
if IsWindowVisible(hWndParent) && !IsWindowVisible(view.hWnd) {
view.isHidden = false
}
return DefWindowProcW(view.hWnd, UINT(WM_PAINT), 0, 0)
}
break
case UINT(ODA_FOCUS):
// TODO(compnerd) figure out how to deal with focus
return LRESULT(1)
default:
fatalError("unhandled message: \(lpDrawItem.pointee.itemAction)")
}
case UINT(WM_MEASUREITEM):
let lpMeasurement: UnsafeMutablePointer<MEASUREITEMSTRUCT> =
UnsafeMutablePointer<MEASUREITEMSTRUCT>(bitPattern: UInt(lParam))!
if let view = unsafeBitCast(lpMeasurement.pointee.itemData,
to: AnyObject.self) as? View {
lpMeasurement.pointee.itemHeight = UINT(view.frame.size.height)
lpMeasurement.pointee.itemWidth = UINT(view.frame.size.width)
}
return LRESULT(1)
case UINT(WM_DELETEITEM):
let lpDeleteItem: UnsafeMutablePointer<DELETEITEMSTRUCT> =
UnsafeMutablePointer<DELETEITEMSTRUCT>(bitPattern: UInt(lParam))!
if let view = unsafeBitCast(lpDeleteItem.pointee.itemData,
to: AnyObject.self) as? View {
view.removeFromSuperview()
}
return LRESULT(1)
default: break
}
return DefWindowProcW(hWnd, uMsg, wParam, lParam)
}
private class TableViewProxy {
private static let `class`: WindowClass =
WindowClass(hInst: GetModuleHandleW(nil), name: "Swift.TableView.Proxy",
WindowProc: SwiftTableViewProxyWindowProc)
fileprivate var hWnd: HWND!
fileprivate init() {
_ = TableViewProxy.class.register()
self.hWnd = CreateWindowExW(0, TableViewProxy.class.name, nil, 0, 0, 0, 0, 0,
HWND_MESSAGE, nil, GetModuleHandleW(nil), nil)!
}
deinit {
_ = DestroyWindow(self.hWnd)
_ = TableViewProxy.class.unregister()
}
}
extension TableView {
public enum Style: Int {
/// A plain table view.
case plain
/// A table view where sections have distinct groups of rows.
case group
/// A table view where the grouped sections are inset with rounded corners.
case insetGrouped
}
}
/// A view that presents data using rows arranged in a single column.
public class TableView: View {
private static let `class`: WindowClass = WindowClass(named: WC_LISTBOX)
private static let style: WindowStyle =
(base: WS_BORDER | WS_HSCROLL | WS_POPUP | WS_TABSTOP | WS_VSCROLL | DWORD(LBS_NODATA | LBS_OWNERDRAWVARIABLE),
extended: 0)
private static let proxy: TableViewProxy = TableViewProxy()
// MARK - Creating a Table View
/// Initializes and returns a table view having the given frame and style.
public init(frame: Rect, style: TableView.Style) {
self.style = style
super.init(frame: frame, class: TableView.class, style: TableView.style,
parent: TableView.proxy.hWnd)
}
// MARK - Providing the Table's Data and Cells
/// The object that acts as the data source of the table view.
public weak var dataSource: TableViewDataSource? {
didSet { self.reloadData() }
}
// MARK - Configuring the Table's Appearance
/// The style of the table view.
public let style: TableView.Style
// MARK - Selecting Rows
/// A boolean value that determines whether users can select more than one row
/// outside of editing mode.
public var allowsMultipleSelection: Bool {
get { self.GWL_STYLE & LBS_EXTENDEDSEL == LBS_EXTENDEDSEL }
set {
self.GWL_STYLE = (self.GWL_STYLE & ~LBS_EXTENDEDSEL)
| (newValue ? 0 : LBS_EXTENDEDSEL)
}
}
// MARK - Reloading the Table View
/// Reloads the rows and sections of the table view.
public func reloadData() {
_ = SendMessageW(self.hWnd, UINT(LB_RESETCONTENT), 0, 0)
guard let dataSource = self.dataSource else { return }
// Suspend redraws while repopulating the view.
_ = SendMessageW(self.hWnd, UINT(WM_SETREDRAW), 0, 0)
for section in 0 ..< dataSource.numberOfSections(in: self) {
for row in 0 ..< dataSource.tableView(self,
numberOfRowsInSection: section) {
let cell =
dataSource.tableView(self,
cellForRowAt: IndexPath(row: row,
section: section))
// Resize the frame to the size that fits the content
cell.frame.size = cell.sizeThatFits(cell.frame.size)
_ = SendMessageW(self.hWnd, UINT(LB_INSERTSTRING),
WPARAM(bitPattern: -1),
unsafeBitCast(cell as AnyObject, to: LPARAM.self))
addSubview(cell)
}
}
// Resume redraws.
_ = SendMessageW(self.hWnd, UINT(WM_SETREDRAW), 1, 0)
}
}