PlatformGlfw.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. package imgui
  2. import (
  3. "fmt"
  4. "image"
  5. "math"
  6. "runtime"
  7. "github.com/go-gl/glfw/v3.3/glfw"
  8. )
  9. var GlfwDontCare int = glfw.DontCare
  10. type GLFWClipboard struct {
  11. window *glfw.Window
  12. }
  13. func NewGLFWClipboard(w *glfw.Window) *GLFWClipboard {
  14. return &GLFWClipboard{window: w}
  15. }
  16. func (c *GLFWClipboard) Text() (string, error) {
  17. return c.window.GetClipboardString(), nil
  18. }
  19. func (c *GLFWClipboard) SetText(text string) {
  20. c.window.SetClipboardString(text)
  21. }
  22. type GLFWWindowFlags uint8
  23. const (
  24. GLFWWindowFlagsNotResizable GLFWWindowFlags = 1 << iota
  25. GLFWWindowFlagsMaximized
  26. GLFWWindowFlagsFloating
  27. GLFWWindowFlagsFrameless
  28. GLFWWindowFlagsTransparent
  29. )
  30. // GLFW implements a platform based on github.com/go-gl/glfw (v3.3).
  31. type GLFW struct {
  32. imguiIO IO
  33. window *glfw.Window
  34. tps int
  35. time float64
  36. mouseJustPressed [3]bool
  37. mouseCursors map[int]*glfw.Cursor
  38. posChangeCallback func(int, int)
  39. sizeChangeCallback func(int, int)
  40. dropCallback func([]string)
  41. inputCallback func(key glfw.Key, mods glfw.ModifierKey, action glfw.Action)
  42. closeCallback func() bool
  43. }
  44. // NewGLFW attempts to initialize a GLFW context.
  45. func NewGLFW(io IO, title string, width, height int, flags GLFWWindowFlags) (*GLFW, error) {
  46. runtime.LockOSThread()
  47. err := glfw.Init()
  48. if err != nil {
  49. return nil, fmt.Errorf("failed to initialize glfw: %v", err)
  50. }
  51. glfw.WindowHint(glfw.ContextVersionMajor, 3)
  52. glfw.WindowHint(glfw.ContextVersionMinor, 3)
  53. glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
  54. glfw.WindowHint(glfw.OpenGLForwardCompatible, 1)
  55. glfw.WindowHint(glfw.ScaleToMonitor, glfw.True)
  56. glfw.WindowHint(glfw.Visible, glfw.False)
  57. if flags&GLFWWindowFlagsNotResizable != 0 {
  58. glfw.WindowHint(glfw.Resizable, glfw.False)
  59. }
  60. if flags&GLFWWindowFlagsMaximized != 0 {
  61. glfw.WindowHint(glfw.Maximized, glfw.True)
  62. }
  63. if flags&GLFWWindowFlagsFloating != 0 {
  64. glfw.WindowHint(glfw.Floating, glfw.True)
  65. }
  66. if flags&GLFWWindowFlagsFrameless != 0 {
  67. glfw.WindowHint(glfw.Decorated, glfw.False)
  68. }
  69. if flags&GLFWWindowFlagsTransparent != 0 {
  70. glfw.WindowHint(glfw.TransparentFramebuffer, glfw.True)
  71. }
  72. window, err := glfw.CreateWindow(width, height, title, nil, nil)
  73. if err != nil {
  74. glfw.Terminate()
  75. return nil, fmt.Errorf("failed to create window: %v", err)
  76. }
  77. window.MakeContextCurrent()
  78. glfw.SwapInterval(1)
  79. platform := &GLFW{
  80. imguiIO: io,
  81. window: window,
  82. tps: 60,
  83. }
  84. platform.setKeyMapping()
  85. platform.installCallbacks()
  86. // Create mosue cursors
  87. platform.mouseCursors = make(map[int]*glfw.Cursor)
  88. platform.mouseCursors[MouseCursorArrow] = glfw.CreateStandardCursor(glfw.ArrowCursor)
  89. platform.mouseCursors[MouseCursorTextInput] = glfw.CreateStandardCursor(glfw.IBeamCursor)
  90. platform.mouseCursors[MouseCursorResizeAll] = glfw.CreateStandardCursor(glfw.CrosshairCursor)
  91. platform.mouseCursors[MouseCursorHand] = glfw.CreateStandardCursor(glfw.HandCursor)
  92. platform.mouseCursors[MouseCursorResizeEW] = glfw.CreateStandardCursor(glfw.HResizeCursor)
  93. platform.mouseCursors[MouseCursorResizeNS] = glfw.CreateStandardCursor(glfw.VResizeCursor)
  94. io.SetClipboard(NewGLFWClipboard(window))
  95. if flags&GLFWWindowFlagsMaximized == 0 {
  96. // Center window to monitor
  97. platform.centerWindow()
  98. }
  99. platform.window.Show()
  100. return platform, nil
  101. }
  102. // Dispose cleans up the resources.
  103. func (platform *GLFW) Dispose() {
  104. platform.window.Destroy()
  105. glfw.Terminate()
  106. }
  107. func (platform *GLFW) GetContentScale() float32 {
  108. x, _ := platform.window.GetContentScale()
  109. // Do not scale on MacOS
  110. if runtime.GOOS == "darwin" {
  111. x = 1
  112. }
  113. return x
  114. }
  115. func (platform *GLFW) GetWindow() *glfw.Window {
  116. return platform.window
  117. }
  118. func (platform *GLFW) GetPos() (x, y int) {
  119. return platform.window.GetPos()
  120. }
  121. func (platform *GLFW) centerWindow() {
  122. monitor := platform.getBestMonitor()
  123. if monitor == nil {
  124. return
  125. }
  126. mode := monitor.GetVideoMode()
  127. if mode == nil {
  128. return
  129. }
  130. monitorX, monitorY := monitor.GetPos()
  131. windowWidth, windowHeight := platform.window.GetSize()
  132. platform.window.SetPos(monitorX+(mode.Width-windowWidth)/2, monitorY+(mode.Height-windowHeight)/2)
  133. }
  134. func (platform *GLFW) getBestMonitor() *glfw.Monitor {
  135. monitors := glfw.GetMonitors()
  136. if len(monitors) == 0 {
  137. return nil
  138. }
  139. width, height := platform.window.GetSize()
  140. x, y := platform.window.GetPos()
  141. var bestMonitor *glfw.Monitor
  142. var bestArea int
  143. for _, m := range monitors {
  144. monitorX, monitorY := m.GetPos()
  145. mode := m.GetVideoMode()
  146. if mode == nil {
  147. continue
  148. }
  149. areaMinX := int(math.Max(float64(x), float64(monitorX)))
  150. areaMinY := int(math.Max(float64(y), float64(monitorY)))
  151. areaMaxX := int(math.Min(float64(x+width), float64(monitorX+mode.Width)))
  152. areaMaxY := int(math.Min(float64(y+height), float64(monitorY+mode.Height)))
  153. area := (areaMaxX - areaMinX) * (areaMaxY - areaMinY)
  154. if area > bestArea {
  155. bestArea = area
  156. bestMonitor = m
  157. }
  158. }
  159. return bestMonitor
  160. }
  161. // ShouldStop returns true if the window is to be closed.
  162. func (platform *GLFW) ShouldStop() bool {
  163. return platform.window.ShouldClose()
  164. }
  165. // SetShouldStop sets whether window should be closed
  166. func (platform *GLFW) SetShouldStop(v bool) {
  167. platform.window.SetShouldClose(v)
  168. }
  169. func (platform *GLFW) WaitForEvent() {
  170. if platform.imguiIO.GetConfigFlags()&ConfigFlagEnablePowerSavingMode == 0 {
  171. return
  172. }
  173. windowIsHidden := platform.window.GetAttrib(glfw.Visible) == glfw.False || platform.window.GetAttrib(glfw.Iconified) == glfw.True
  174. waitingTime := math.Inf(0)
  175. if !windowIsHidden {
  176. waitingTime = GetEventWaitingTime()
  177. }
  178. if waitingTime > 0 {
  179. if math.IsInf(waitingTime, 0) {
  180. glfw.WaitEvents()
  181. } else {
  182. glfw.WaitEventsTimeout(waitingTime)
  183. }
  184. }
  185. }
  186. // ProcessEvents handles all pending window events.
  187. func (platform *GLFW) ProcessEvents() {
  188. platform.WaitForEvent()
  189. glfw.PollEvents()
  190. }
  191. // DisplaySize returns the dimension of the display.
  192. func (platform *GLFW) DisplaySize() [2]float32 {
  193. w, h := platform.window.GetSize()
  194. return [2]float32{float32(w), float32(h)}
  195. }
  196. // FramebufferSize returns the dimension of the framebuffer.
  197. func (platform *GLFW) FramebufferSize() [2]float32 {
  198. w, h := platform.window.GetFramebufferSize()
  199. return [2]float32{float32(w), float32(h)}
  200. }
  201. // NewFrame marks the begin of a render pass. It forwards all current state to imgui IO.
  202. func (platform *GLFW) NewFrame() {
  203. // Setup display size (every frame to accommodate for window resizing)
  204. displaySize := platform.DisplaySize()
  205. platform.imguiIO.SetDisplaySize(Vec2{X: displaySize[0], Y: displaySize[1]})
  206. // Setup time step
  207. currentTime := glfw.GetTime()
  208. if platform.time > 0 {
  209. platform.imguiIO.SetDeltaTime(float32(currentTime - platform.time))
  210. }
  211. platform.time = currentTime
  212. // Setup inputs
  213. if platform.window.GetAttrib(glfw.Focused) != 0 {
  214. x, y := platform.window.GetCursorPos()
  215. platform.imguiIO.SetMousePosition(Vec2{X: float32(x), Y: float32(y)})
  216. } else {
  217. platform.imguiIO.SetMousePosition(Vec2{X: -math.MaxFloat32, Y: -math.MaxFloat32})
  218. }
  219. for i := 0; i < len(platform.mouseJustPressed); i++ {
  220. down := platform.mouseJustPressed[i] || (platform.window.GetMouseButton(glfwButtonIDByIndex[i]) == glfw.Press)
  221. platform.imguiIO.SetMouseButtonDown(i, down)
  222. platform.mouseJustPressed[i] = false
  223. }
  224. platform.updateMouseCursor()
  225. }
  226. // PostRender performs a buffer swap.
  227. func (platform *GLFW) PostRender() {
  228. platform.window.SwapBuffers()
  229. }
  230. func (platform *GLFW) SetPosChangeCallback(cb func(int, int)) {
  231. platform.posChangeCallback = cb
  232. }
  233. func (platform *GLFW) SetSizeChangeCallback(cb func(int, int)) {
  234. platform.sizeChangeCallback = cb
  235. }
  236. func (platform *GLFW) Update() {
  237. glfw.PostEmptyEvent()
  238. }
  239. func (platform *GLFW) SetDropCallback(cb func(names []string)) {
  240. platform.dropCallback = cb
  241. }
  242. func (platform *GLFW) SetInputCallback(cb func(key glfw.Key, mods glfw.ModifierKey, action glfw.Action)) {
  243. platform.inputCallback = cb
  244. }
  245. func (platform *GLFW) SetCloseCallback(cb func() bool) {
  246. platform.closeCallback = cb
  247. }
  248. func (platform *GLFW) updateMouseCursor() {
  249. io := platform.imguiIO
  250. if (io.GetConfigFlags()&ConfigFlagNoMouseCursorChange) == 1 || platform.window.GetInputMode(glfw.CursorMode) == glfw.CursorDisabled {
  251. return
  252. }
  253. cursor := MouseCursor()
  254. if cursor == MouseCursorNone || io.GetMouseDrawCursor() {
  255. platform.window.SetInputMode(glfw.CursorMode, glfw.CursorHidden)
  256. } else {
  257. gCursor := platform.mouseCursors[MouseCursorArrow]
  258. if c, ok := platform.mouseCursors[cursor]; ok {
  259. gCursor = c
  260. }
  261. platform.window.SetCursor(gCursor)
  262. platform.window.SetInputMode(glfw.CursorMode, glfw.CursorNormal)
  263. }
  264. }
  265. func (platform *GLFW) setKeyMapping() {
  266. // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
  267. platform.imguiIO.KeyMap(KeyTab, int(glfw.KeyTab))
  268. platform.imguiIO.KeyMap(KeyLeftArrow, int(glfw.KeyLeft))
  269. platform.imguiIO.KeyMap(KeyRightArrow, int(glfw.KeyRight))
  270. platform.imguiIO.KeyMap(KeyUpArrow, int(glfw.KeyUp))
  271. platform.imguiIO.KeyMap(KeyDownArrow, int(glfw.KeyDown))
  272. platform.imguiIO.KeyMap(KeyPageUp, int(glfw.KeyPageUp))
  273. platform.imguiIO.KeyMap(KeyPageDown, int(glfw.KeyPageDown))
  274. platform.imguiIO.KeyMap(KeyHome, int(glfw.KeyHome))
  275. platform.imguiIO.KeyMap(KeyEnd, int(glfw.KeyEnd))
  276. platform.imguiIO.KeyMap(KeyInsert, int(glfw.KeyInsert))
  277. platform.imguiIO.KeyMap(KeyDelete, int(glfw.KeyDelete))
  278. platform.imguiIO.KeyMap(KeyBackspace, int(glfw.KeyBackspace))
  279. platform.imguiIO.KeyMap(KeySpace, int(glfw.KeySpace))
  280. platform.imguiIO.KeyMap(KeyEnter, int(glfw.KeyEnter))
  281. platform.imguiIO.KeyMap(KeyEscape, int(glfw.KeyEscape))
  282. platform.imguiIO.KeyMap(KeyA, int(glfw.KeyA))
  283. platform.imguiIO.KeyMap(KeyC, int(glfw.KeyC))
  284. platform.imguiIO.KeyMap(KeyV, int(glfw.KeyV))
  285. platform.imguiIO.KeyMap(KeyX, int(glfw.KeyX))
  286. platform.imguiIO.KeyMap(KeyY, int(glfw.KeyY))
  287. platform.imguiIO.KeyMap(KeyZ, int(glfw.KeyZ))
  288. }
  289. func (platform *GLFW) installCallbacks() {
  290. platform.window.SetMouseButtonCallback(platform.mouseButtonChange)
  291. platform.window.SetScrollCallback(platform.mouseScrollChange)
  292. platform.window.SetKeyCallback(platform.keyChange)
  293. platform.window.SetCharCallback(platform.charChange)
  294. platform.window.SetSizeCallback(platform.sizeChange)
  295. platform.window.SetDropCallback(platform.onDrop)
  296. platform.window.SetPosCallback(platform.posChange)
  297. platform.window.SetCloseCallback(platform.onClose)
  298. platform.window.SetFocusCallback(platform.onFocus)
  299. }
  300. var glfwButtonIndexByID = map[glfw.MouseButton]int{
  301. glfw.MouseButton1: 0,
  302. glfw.MouseButton2: 1,
  303. glfw.MouseButton3: 2,
  304. }
  305. var glfwButtonIDByIndex = map[int]glfw.MouseButton{
  306. 0: glfw.MouseButton1,
  307. 1: glfw.MouseButton2,
  308. 2: glfw.MouseButton3,
  309. }
  310. func (platform *GLFW) onFocus(window *glfw.Window, focused bool) {
  311. platform.imguiIO.AddFocusEvent(focused)
  312. }
  313. func (platform *GLFW) onClose(window *glfw.Window) {
  314. if platform.closeCallback != nil {
  315. shouldClose := platform.closeCallback()
  316. window.SetShouldClose(shouldClose)
  317. }
  318. }
  319. func (platform *GLFW) onDrop(window *glfw.Window, names []string) {
  320. window.Focus()
  321. if platform.dropCallback != nil {
  322. platform.dropCallback(names)
  323. }
  324. }
  325. func (platform *GLFW) posChange(window *glfw.Window, x, y int) {
  326. platform.imguiIO.SetFrameCountSinceLastInput(0)
  327. // Notfy pos changed and redraw.
  328. if platform.posChangeCallback != nil {
  329. platform.posChangeCallback(x, y)
  330. }
  331. }
  332. func (platform *GLFW) sizeChange(window *glfw.Window, width, height int) {
  333. platform.imguiIO.SetFrameCountSinceLastInput(0)
  334. // Notify size changed and redraw.
  335. if platform.sizeChangeCallback != nil {
  336. platform.sizeChangeCallback(width, height)
  337. }
  338. }
  339. func (platform *GLFW) mouseButtonChange(window *glfw.Window, rawButton glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) {
  340. platform.imguiIO.SetFrameCountSinceLastInput(0)
  341. buttonIndex, known := glfwButtonIndexByID[rawButton]
  342. if known && (action == glfw.Press) {
  343. platform.mouseJustPressed[buttonIndex] = true
  344. }
  345. }
  346. func (platform *GLFW) mouseScrollChange(window *glfw.Window, x, y float64) {
  347. platform.imguiIO.SetFrameCountSinceLastInput(0)
  348. platform.imguiIO.AddMouseWheelDelta(float32(x), float32(y))
  349. }
  350. func (platform *GLFW) keyChange(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
  351. platform.imguiIO.SetFrameCountSinceLastInput(0)
  352. if action == glfw.Press {
  353. platform.imguiIO.KeyPress(int(key))
  354. }
  355. if action == glfw.Release {
  356. platform.imguiIO.KeyRelease(int(key))
  357. }
  358. // Modifiers are not reliable across systems
  359. platform.imguiIO.KeyCtrl(int(glfw.KeyLeftControl), int(glfw.KeyRightControl))
  360. platform.imguiIO.KeyShift(int(glfw.KeyLeftShift), int(glfw.KeyRightShift))
  361. platform.imguiIO.KeyAlt(int(glfw.KeyLeftAlt), int(glfw.KeyRightAlt))
  362. platform.imguiIO.KeySuper(int(glfw.KeyLeftSuper), int(glfw.KeyRightSuper))
  363. if platform.inputCallback != nil {
  364. platform.inputCallback(key, mods, action)
  365. }
  366. }
  367. func (platform *GLFW) charChange(window *glfw.Window, char rune) {
  368. platform.imguiIO.SetFrameCountSinceLastInput(0)
  369. platform.imguiIO.AddInputCharacters(string(char))
  370. }
  371. func (platform *GLFW) GetClipboard() string {
  372. return platform.window.GetClipboardString()
  373. }
  374. func (platform *GLFW) SetClipboard(content string) {
  375. platform.window.SetClipboardString(content)
  376. }
  377. func (platform *GLFW) GetTPS() int {
  378. return platform.tps
  379. }
  380. func (platform *GLFW) SetTPS(tps int) {
  381. platform.tps = tps
  382. }
  383. // SetIcon sets the icon of the specified window. If passed an array of candidate images,
  384. // those of or closest to the sizes desired by the system are selected. If no images are
  385. // specified, the window reverts to its default icon.
  386. //
  387. // The image is ideally provided in the form of *image.NRGBA.
  388. // The pixels are 32-bit, little-endian, non-premultiplied RGBA, i.e. eight
  389. // bits per channel with the red channel first. They are arranged canonically
  390. // as packed sequential rows, starting from the top-left corner. If the image
  391. // type is not *image.NRGBA, it will be converted to it.
  392. //
  393. // The desired image sizes varies depending on platform and system settings. The selected
  394. // images will be rescaled as needed. Good sizes include 16x16, 32x32 and 48x48.
  395. func (platform *GLFW) SetIcon(icons []image.Image) {
  396. platform.window.SetIcon(icons)
  397. }
  398. // SetSizeLimits sets the size limits of the client area of the specified window.
  399. // If the window is full screen or not resizable, this function does nothing.
  400. //
  401. // The size limits are applied immediately and may cause the window to be resized.
  402. // To specify only a minimum size or only a maximum one, set the other pair to GLFW_DONT_CARE.
  403. // To disable size limits for a window, set them all to GLFW_DONT_CARE.
  404. func (platform *GLFW) SetSizeLimits(minw, minh, maxw, maxh int) {
  405. platform.window.SetSizeLimits(minw, minh, maxw, maxh)
  406. }
  407. // SetTitle sets the title of window.
  408. func (platform *GLFW) SetTitle(title string) {
  409. platform.window.SetTitle(title)
  410. }
  411. // IsMinimized checks whether window is minimized.
  412. func (platform *GLFW) IsMinimized() bool {
  413. return glfw.True == platform.window.GetAttrib(glfw.Iconified)
  414. }
  415. // IsVisible checks whether window is visible.
  416. func (platform *GLFW) IsVisible() bool {
  417. return glfw.True == platform.window.GetAttrib(glfw.Visible)
  418. }