-
Notifications
You must be signed in to change notification settings - Fork 283
/
glfw.go
290 lines (253 loc) · 9.98 KB
/
glfw.go
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
package flutter
import (
"fmt"
"runtime"
"unsafe"
"github.com/go-flutter-desktop/go-flutter/embedder"
"github.com/go-gl/glfw/v3.3/glfw"
)
// dpPerInch defines the amount of display pixels per inch as defined for Flutter.
const dpPerInch = 160.0
// TODO (GeertJohan): better name for this, confusing with 'actual' window
// managers. Renderer interface? implemented by this type for glfw? type
// glfwRenderer or glfwManager? All the attaching to glfw.Window must be done
// during manager init in that case. Cannot be done by Application.
type windowManager struct {
// forcedPixelRatio forces the pixelRatio to given value, when value is not zero.
forcedPixelRatio float64
// current pointer state
pointerPhase embedder.PointerPhase
pointerButton embedder.PointerButtonMouse
pointerCurrentlyAdded bool
// caching of ppsc to avoid re-calculating every event
pixelsPerScreenCoordinate float64
}
func newWindowManager(forcedPixelRatio float64) *windowManager {
return &windowManager{
forcedPixelRatio: forcedPixelRatio,
pixelsPerScreenCoordinate: 1.0,
pointerPhase: embedder.PointerPhaseHover,
}
}
func (m *windowManager) sendPointerEvent(window *glfw.Window, phase embedder.PointerPhase, x, y float64) {
// synthesize an PointerPhaseAdd if the pointer isn't already added
if !m.pointerCurrentlyAdded && phase != embedder.PointerPhaseAdd {
m.sendPointerEvent(window, embedder.PointerPhaseAdd, x, y)
}
// Don't double-add the pointer
if m.pointerCurrentlyAdded && phase == embedder.PointerPhaseAdd {
return
}
event := embedder.PointerEvent{
Phase: phase,
X: x * m.pixelsPerScreenCoordinate,
Y: y * m.pixelsPerScreenCoordinate,
Buttons: m.pointerButton,
}
flutterEnginePointer := *(*uintptr)(window.GetUserPointer())
flutterEngine := (*embedder.FlutterEngine)(unsafe.Pointer(flutterEnginePointer))
// Always send a pointer event with PhaseMove before an eventual PhaseRemove.
// If x/y on the last move doesn't equal x/y on the PhaseRemove, the remove
// is canceled in Flutter.
if phase == embedder.PointerPhaseRemove {
event.Phase = embedder.PointerPhaseHover
flutterEngine.SendPointerEvent(event)
event.Phase = embedder.PointerPhaseRemove
}
flutterEngine.SendPointerEvent(event)
if phase == embedder.PointerPhaseAdd {
m.pointerCurrentlyAdded = true
} else if phase == embedder.PointerPhaseRemove {
m.pointerCurrentlyAdded = false
}
}
func (m *windowManager) sendPointerEventButton(window *glfw.Window, phase embedder.PointerPhase) {
x, y := window.GetCursorPos()
event := embedder.PointerEvent{
Phase: phase,
X: x * m.pixelsPerScreenCoordinate,
Y: y * m.pixelsPerScreenCoordinate,
SignalKind: embedder.PointerSignalKindNone,
Buttons: m.pointerButton,
}
flutterEnginePointer := *(*uintptr)(window.GetUserPointer())
flutterEngine := (*embedder.FlutterEngine)(unsafe.Pointer(flutterEnginePointer))
flutterEngine.SendPointerEvent(event)
}
func (m *windowManager) sendPointerEventScroll(window *glfw.Window, xDelta, yDelta float64) {
x, y := window.GetCursorPos()
event := embedder.PointerEvent{
Phase: m.pointerPhase,
X: x * m.pixelsPerScreenCoordinate,
Y: y * m.pixelsPerScreenCoordinate,
SignalKind: embedder.PointerSignalKindScroll,
ScrollDeltaX: xDelta,
ScrollDeltaY: yDelta,
Buttons: m.pointerButton,
}
flutterEnginePointer := *(*uintptr)(window.GetUserPointer())
flutterEngine := (*embedder.FlutterEngine)(unsafe.Pointer(flutterEnginePointer))
flutterEngine.SendPointerEvent(event)
}
func (m *windowManager) glfwCursorEnterCallback(window *glfw.Window, entered bool) {
x, y := window.GetCursorPos()
if entered {
m.sendPointerEvent(window, embedder.PointerPhaseAdd, x, y)
// the mouse can enter the windows while having button pressed.
// if so, don't overwrite the phase.
if m.pointerButton == 0 {
m.pointerPhase = embedder.PointerPhaseHover
}
} else {
// if the mouse is still in 'phaseMove' outside the window (click-drag
// outside). Don't remove the cursor.
if m.pointerButton == 0 {
m.sendPointerEvent(window, embedder.PointerPhaseRemove, x, y)
}
}
}
func (m *windowManager) glfwCursorPosCallback(window *glfw.Window, x, y float64) {
m.sendPointerEvent(window, m.pointerPhase, x, y)
}
func (m *windowManager) handleButtonPhase(window *glfw.Window, action glfw.Action, buttons embedder.PointerButtonMouse) {
if action == glfw.Press {
m.pointerButton |= buttons
// If only one button is pressed then each bits of buttons will be equals
// to m.pointerButton.
if m.pointerButton == buttons {
m.sendPointerEventButton(window, embedder.PointerPhaseDown)
} else {
// if any other buttons are already pressed when a new button is pressed,
// the engine is expecting a Move phase instead of a Down phase.
m.sendPointerEventButton(window, embedder.PointerPhaseMove)
}
m.pointerPhase = embedder.PointerPhaseMove
}
if action == glfw.Release {
// Always send a pointer event with PhaseMove before an eventual
// PhaseUp. Even if the last button was released. If x/y on the last
// move doesn't equal x/y on the PhaseUp, the click is canceled in
// Flutter. On MacOS, the Release event always has y-1 of the last move
// event. By sending a PhaseMove here (after the release) we avoid a
// difference in x/y.
m.sendPointerEventButton(window, embedder.PointerPhaseMove)
m.pointerButton ^= buttons
// If all button are released then m.pointerButton is cleared
if m.pointerButton == 0 {
m.sendPointerEventButton(window, embedder.PointerPhaseUp)
m.pointerPhase = embedder.PointerPhaseHover
} else {
// if any other buttons are still pressed when one button is released
// the engine is expecting a Move phase instead of a Up phase.
m.sendPointerEventButton(window, embedder.PointerPhaseMove)
}
}
}
func (m *windowManager) glfwMouseButtonCallback(window *glfw.Window, key glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) {
switch key {
case glfw.MouseButtonLeft:
m.handleButtonPhase(window, action, embedder.PointerButtonMousePrimary)
case glfw.MouseButtonRight:
m.handleButtonPhase(window, action, embedder.PointerButtonMouseSecondary)
case glfw.MouseButtonMiddle:
m.handleButtonPhase(window, action, embedder.PointerButtonMouseMiddle)
default:
m.handleButtonPhase(window, action, 1<<uint(key))
}
}
func (m *windowManager) glfwScrollCallback(window *glfw.Window, xoff float64, yoff float64, scrollAmount float64) {
scrollModifier := -scrollAmount
m.sendPointerEventScroll(window, xoff*scrollModifier, yoff*scrollModifier)
}
// glfwRefreshCallback is called when the window needs a reresh, this
// can occur when the window is resized, was covered by another window, etc.
// When forcedPixelratio is zero, the forcedPixelratio communicated to the
// Flutter embedder is calculated based on physical and logical screen
// dimensions.
func (m *windowManager) glfwRefreshCallback(window *glfw.Window) {
widthPx, heightPx := window.GetFramebufferSize()
width, _ := window.GetSize()
if width == 0 {
fmt.Println("go-flutter: Cannot calculate pixelsPerScreenCoordinate for zero-width window.")
return
}
m.pixelsPerScreenCoordinate = float64(widthPx) / float64(width)
var pixelRatio float64
if m.forcedPixelRatio != 0 {
pixelRatio = m.forcedPixelRatio
} else {
if runtime.GOOS == "linux" {
pixelRatio = m.getPixelRatioLinux(window)
} else {
pixelRatio = m.getPixelRatioOther(window)
}
}
event := embedder.WindowMetricsEvent{
Width: widthPx,
Height: heightPx,
PixelRatio: pixelRatio,
}
flutterEnginePointer := *(*uintptr)(window.GetUserPointer())
flutterEngine := (*embedder.FlutterEngine)(unsafe.Pointer(flutterEnginePointer))
flutterEngine.SendWindowMetricsEvent(event)
}
// getPixelRatioOther, getPixelRatioLinux isn't well working on other platform.
// GLFW window.GetContentScale() works better:
// https://github.com/go-flutter-desktop/go-flutter/pull/458
func (m *windowManager) getPixelRatioOther(window *glfw.Window) float64 {
xscale, _ := window.GetContentScale()
return float64(xscale)
}
// getPixelRatioLinux returns the Flutter pixel_ratio is defined as DPI/dp
// given framebuffer size and the current window information.
// Same as defined in the official LINUX embedder:
// https://github.com/flutter/engine/blob/master/shell/platform/glfw/flutter_glfw.cc
// Fallback to getPixelRatioOther if error occur.
func (m *windowManager) getPixelRatioLinux(window *glfw.Window) float64 {
widthPx, heightPx := window.GetFramebufferSize()
var selectedMonitor *glfw.Monitor
winX, winY := window.GetPos()
winCenterX, winCenterY := winX+widthPx/2, winY+heightPx/2
monitors := glfw.GetMonitors()
for _, monitor := range monitors {
monX1, monY1 := monitor.GetPos()
monMode := monitor.GetVideoMode()
if monMode == nil {
continue
}
monX2, monY2 := monX1+monMode.Width, monY1+monMode.Height
if (monX1 <= winCenterX && winCenterX <= monX2) &&
(monY1 <= winCenterY && winCenterY <= monY2) {
selectedMonitor = monitor
break
}
}
if selectedMonitor == nil {
// when no monitor was selected, try fallback to primary monitor
// TODO: ? perhaps select monitor that is "closest" to the window ?
selectedMonitor = glfw.GetPrimaryMonitor()
}
if selectedMonitor == nil {
return m.getPixelRatioOther(window)
}
selectedMonitorMode := selectedMonitor.GetVideoMode()
if selectedMonitorMode == nil {
return m.getPixelRatioOther(window)
}
selectedMonitorWidthMM, _ := selectedMonitor.GetPhysicalSize()
if selectedMonitorWidthMM == 0 {
return m.getPixelRatioOther(window)
}
monitorScreenCoordinatesPerInch := float64(selectedMonitorMode.Width) / (float64(selectedMonitorWidthMM) / 25.4)
dpi := m.pixelsPerScreenCoordinate * monitorScreenCoordinatesPerInch
pixelRatio := dpi / dpPerInch
// If the pixelRatio is lower than 1 use this pixelRatio factor to downscale the ContentScale
if pixelRatio < 1.0 {
pixelRatio *= m.getPixelRatioOther(window)
}
// If it is still lower than 1, fallback to a pixelRatio of 1.0
if pixelRatio < 1.0 {
pixelRatio = 1.0
}
return pixelRatio
}