-
Notifications
You must be signed in to change notification settings - Fork 72
/
Copy pathStepper.swift
178 lines (150 loc) · 5.57 KB
/
Stepper.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
// Copyright © 2020 Saleem Abdulrasool <[email protected]>
// SPDX-License-Identifier: BSD-3-Clause
import WinSDK
// Notification Proxy
// When the Window is created, the initial parent is cached. This cache cannot
// be updated. Instead, we always parent any stepper control to the
// `Swift.Stepper.Proxy` which is process-wide. All notifications
// about the control events will be dispatched by the proxy.
//
// In order to facilitate this, the control will stash the `self` (instance)
// pointer in `GWLP_USERDATA`. When we receive a `WM_HSCROLL` event, the
// `lParam` contains the handle to the control. We use this to query the
// stashed instance pointer and dispatch the action to the control, allowing the
// action targets to be invoked.
private let SwiftStepperWindowProc: WNDPROC = { (hWnd, uMsg, wParam, lParam) in
switch uMsg {
case UINT(WM_HSCROLL):
if let stepper = UserData(from: HWND(bitPattern: UInt(lParam))) as Stepper? {
stepper.sendActions(for: .valueChanged)
}
default:
break
}
return DefWindowProcW(hWnd, uMsg, wParam, lParam)
}
private class StepperProxy {
private static let `class`: WindowClass =
WindowClass(hInst: GetModuleHandleW(nil), name: "Swift.Stepper.Proxy",
WindowProc: SwiftStepperWindowProc)
fileprivate var hWnd: HWND!
fileprivate init() {
_ = StepperProxy.class.register()
self.hWnd = CreateWindowExW(0, StepperProxy.class.name, nil, 0, 0, 0, 0, 0,
HWND_MESSAGE, nil, GetModuleHandleW(nil), nil)!
}
deinit {
_ = DestroyWindow(self.hWnd)
_ = StepperProxy.class.unregister()
}
}
/// A control for incrementing or decrementing a value.
public class Stepper: Control {
private static let `class`: WindowClass = WindowClass(named: UPDOWN_CLASS)
private static let style: WindowStyle =
(base: DWORD(UDS_HORZ) | WS_POPUP | WS_TABSTOP, extended: 0)
private static var proxy: StepperProxy = StepperProxy()
// MARK - Configuring the Stepper
/// A boolean value that determines whether to send value changes during user
/// interaction or after user interaction ends.
public var isContinuous: Bool = true {
didSet { fatalError("\(#function) not yet implemented") }
}
/// A boolean value that determines whether to repeatedly change the stepper's
/// value as the user presses and holds a stepper button.
public var autorepeat: Bool = true {
didSet { fatalError("\(#function) not yet implemented") }
}
/// A boolean value that determines whether the stepper can wrap its value to
/// the minimum or maximum value when incrementing and decrementing the value.
public var wraps: Bool {
get { self.GWL_STYLE & LONG(UDS_WRAP) == LONG(UDS_WRAP) }
set {
self.GWL_STYLE = newValue ? self.GWL_STYLE | LONG(UDS_WRAP)
: self.GWL_STYLE & ~LONG(UDS_WRAP)
}
}
/// The lowest possible numeric value for the stepper.
public var minimumValue: Double {
get {
var value: CInt = 0
_ = withUnsafeMutablePointer(to: &value) {
SendMessageW(self.hWnd, UINT(UDM_GETRANGE32),
WPARAM(UInt(bitPattern: $0)), 0)
}
return Double(value)
}
set {
let newMaximum =
self.maximumValue >= newValue ? self.minimumValue : newValue
_ = SendMessageW(self.hWnd, UINT(UDM_SETRANGE32),
WPARAM(CInt(newValue)), LPARAM(CInt(newMaximum)))
}
}
/// The highest possible numeric value for the stepper.
public var maximumValue: Double {
get {
var value: CInt = 0
_ = withUnsafeMutablePointer(to: &value) {
SendMessageW(self.hWnd, UINT(UDM_GETRANGE32),
0, LPARAM(UInt(bitPattern: $0)))
}
return Double(value)
}
set {
let newMinimum =
self.minimumValue <= newValue ? self.minimumValue : newValue
_ = SendMessageW(self.hWnd, UINT(UDM_SETRANGE32),
WPARAM(CInt(newMinimum)), LPARAM(CInt(newValue)))
}
}
/// The step, or increment, value for the stepper.
public var stepValue: Double {
get {
var value: UDACCEL = UDACCEL(nSec: 0, nInc: 0)
_ = withUnsafeMutablePointer(to: &value) {
SendMessageW(self.hWnd, UINT(UDM_GETACCEL),
WPARAM(MemoryLayout<UDACCEL>.size),
LPARAM(UInt(bitPattern: $0)))
}
return Double(value.nInc)
}
set {
var value: UDACCEL = UDACCEL(nSec: 0, nInc: 0)
_ = withUnsafeMutablePointer(to: &value) {
SendMessageW(self.hWnd, UINT(UDM_GETACCEL),
WPARAM(1), LPARAM(UInt(bitPattern: $0)))
}
value.nInc = UINT(newValue)
_ = withUnsafeMutablePointer(to: &value) {
SendMessageW(self.hWnd, UINT(UDM_SETACCEL),
WPARAM(1), LPARAM(UInt(bitPattern: $0)))
}
}
}
// MARK - Accessing the Stepper's Value
/// The numeric value of the stepper.
public var value: Double {
get {
let lResult: LRESULT = SendMessageW(self.hWnd, UINT(UDM_GETPOS32), 0, 0)
return Double(lResult)
}
set {
_ = SendMessageW(self.hWnd, UINT(UDM_SETPOS32), 0, LPARAM(DWORD(newValue)))
}
}
// MARK -
public init(frame: Rect) {
super.init(frame: frame, class: Stepper.class, style: Stepper.style,
parent: Stepper.proxy.hWnd)
_ = SetWindowLongPtrW(self.hWnd, GWLP_USERDATA,
unsafeBitCast(self as AnyObject, to: LONG_PTR.self))
defer {
self.wraps = false
self.minimumValue = 0.0
self.maximumValue = 100.0
self.stepValue = 1.0
self.value = 0.0
}
}
}