package imgui import ( "fmt" "image" "math" "runtime" "github.com/go-gl/glfw/v3.3/glfw" ) var GlfwDontCare int = glfw.DontCare type GLFWClipboard struct { window *glfw.Window } func NewGLFWClipboard(w *glfw.Window) *GLFWClipboard { return &GLFWClipboard{window: w} } func (c *GLFWClipboard) Text() (string, error) { return c.window.GetClipboardString(), nil } func (c *GLFWClipboard) SetText(text string) { c.window.SetClipboardString(text) } type GLFWWindowFlags uint8 const ( GLFWWindowFlagsNotResizable GLFWWindowFlags = 1 << iota GLFWWindowFlagsMaximized GLFWWindowFlagsFloating GLFWWindowFlagsFrameless GLFWWindowFlagsTransparent ) // GLFW implements a platform based on github.com/go-gl/glfw (v3.3). type GLFW struct { imguiIO IO window *glfw.Window tps int time float64 mouseJustPressed [3]bool mouseCursors map[int]*glfw.Cursor posChangeCallback func(int, int) sizeChangeCallback func(int, int) dropCallback func([]string) inputCallback func(key glfw.Key, mods glfw.ModifierKey, action glfw.Action) closeCallback func() bool } // NewGLFW attempts to initialize a GLFW context. func NewGLFW(io IO, title string, width, height int, flags GLFWWindowFlags) (*GLFW, error) { runtime.LockOSThread() err := glfw.Init() if err != nil { return nil, fmt.Errorf("failed to initialize glfw: %v", err) } glfw.WindowHint(glfw.ContextVersionMajor, 3) glfw.WindowHint(glfw.ContextVersionMinor, 3) glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) glfw.WindowHint(glfw.OpenGLForwardCompatible, 1) glfw.WindowHint(glfw.ScaleToMonitor, glfw.True) glfw.WindowHint(glfw.Visible, glfw.False) if flags&GLFWWindowFlagsNotResizable != 0 { glfw.WindowHint(glfw.Resizable, glfw.False) } if flags&GLFWWindowFlagsMaximized != 0 { glfw.WindowHint(glfw.Maximized, glfw.True) } if flags&GLFWWindowFlagsFloating != 0 { glfw.WindowHint(glfw.Floating, glfw.True) } if flags&GLFWWindowFlagsFrameless != 0 { glfw.WindowHint(glfw.Decorated, glfw.False) } if flags&GLFWWindowFlagsTransparent != 0 { glfw.WindowHint(glfw.TransparentFramebuffer, glfw.True) } window, err := glfw.CreateWindow(width, height, title, nil, nil) if err != nil { glfw.Terminate() return nil, fmt.Errorf("failed to create window: %v", err) } window.MakeContextCurrent() glfw.SwapInterval(1) platform := &GLFW{ imguiIO: io, window: window, tps: 60, } platform.setKeyMapping() platform.installCallbacks() // Create mosue cursors platform.mouseCursors = make(map[int]*glfw.Cursor) platform.mouseCursors[MouseCursorArrow] = glfw.CreateStandardCursor(glfw.ArrowCursor) platform.mouseCursors[MouseCursorTextInput] = glfw.CreateStandardCursor(glfw.IBeamCursor) platform.mouseCursors[MouseCursorResizeAll] = glfw.CreateStandardCursor(glfw.CrosshairCursor) platform.mouseCursors[MouseCursorHand] = glfw.CreateStandardCursor(glfw.HandCursor) platform.mouseCursors[MouseCursorResizeEW] = glfw.CreateStandardCursor(glfw.HResizeCursor) platform.mouseCursors[MouseCursorResizeNS] = glfw.CreateStandardCursor(glfw.VResizeCursor) io.SetClipboard(NewGLFWClipboard(window)) if flags&GLFWWindowFlagsMaximized == 0 { // Center window to monitor platform.centerWindow() } platform.window.Show() return platform, nil } // Dispose cleans up the resources. func (platform *GLFW) Dispose() { platform.window.Destroy() glfw.Terminate() } func (platform *GLFW) GetContentScale() float32 { x, _ := platform.window.GetContentScale() // Do not scale on MacOS if runtime.GOOS == "darwin" { x = 1 } return x } func (platform *GLFW) GetWindow() *glfw.Window { return platform.window } func (platform *GLFW) GetPos() (x, y int) { return platform.window.GetPos() } func (platform *GLFW) centerWindow() { monitor := platform.getBestMonitor() if monitor == nil { return } mode := monitor.GetVideoMode() if mode == nil { return } monitorX, monitorY := monitor.GetPos() windowWidth, windowHeight := platform.window.GetSize() platform.window.SetPos(monitorX+(mode.Width-windowWidth)/2, monitorY+(mode.Height-windowHeight)/2) } func (platform *GLFW) getBestMonitor() *glfw.Monitor { monitors := glfw.GetMonitors() if len(monitors) == 0 { return nil } width, height := platform.window.GetSize() x, y := platform.window.GetPos() var bestMonitor *glfw.Monitor var bestArea int for _, m := range monitors { monitorX, monitorY := m.GetPos() mode := m.GetVideoMode() if mode == nil { continue } areaMinX := int(math.Max(float64(x), float64(monitorX))) areaMinY := int(math.Max(float64(y), float64(monitorY))) areaMaxX := int(math.Min(float64(x+width), float64(monitorX+mode.Width))) areaMaxY := int(math.Min(float64(y+height), float64(monitorY+mode.Height))) area := (areaMaxX - areaMinX) * (areaMaxY - areaMinY) if area > bestArea { bestArea = area bestMonitor = m } } return bestMonitor } // ShouldStop returns true if the window is to be closed. func (platform *GLFW) ShouldStop() bool { return platform.window.ShouldClose() } // SetShouldStop sets whether window should be closed func (platform *GLFW) SetShouldStop(v bool) { platform.window.SetShouldClose(v) } func (platform *GLFW) WaitForEvent() { if platform.imguiIO.GetConfigFlags()&ConfigFlagEnablePowerSavingMode == 0 { return } windowIsHidden := platform.window.GetAttrib(glfw.Visible) == glfw.False || platform.window.GetAttrib(glfw.Iconified) == glfw.True waitingTime := math.Inf(0) if !windowIsHidden { waitingTime = GetEventWaitingTime() } if waitingTime > 0 { if math.IsInf(waitingTime, 0) { glfw.WaitEvents() } else { glfw.WaitEventsTimeout(waitingTime) } } } // ProcessEvents handles all pending window events. func (platform *GLFW) ProcessEvents() { platform.WaitForEvent() glfw.PollEvents() } // DisplaySize returns the dimension of the display. func (platform *GLFW) DisplaySize() [2]float32 { w, h := platform.window.GetSize() return [2]float32{float32(w), float32(h)} } // FramebufferSize returns the dimension of the framebuffer. func (platform *GLFW) FramebufferSize() [2]float32 { w, h := platform.window.GetFramebufferSize() return [2]float32{float32(w), float32(h)} } // NewFrame marks the begin of a render pass. It forwards all current state to imgui IO. func (platform *GLFW) NewFrame() { // Setup display size (every frame to accommodate for window resizing) displaySize := platform.DisplaySize() platform.imguiIO.SetDisplaySize(Vec2{X: displaySize[0], Y: displaySize[1]}) // Setup time step currentTime := glfw.GetTime() if platform.time > 0 { platform.imguiIO.SetDeltaTime(float32(currentTime - platform.time)) } platform.time = currentTime // Setup inputs if platform.window.GetAttrib(glfw.Focused) != 0 { x, y := platform.window.GetCursorPos() platform.imguiIO.SetMousePosition(Vec2{X: float32(x), Y: float32(y)}) } else { platform.imguiIO.SetMousePosition(Vec2{X: -math.MaxFloat32, Y: -math.MaxFloat32}) } for i := 0; i < len(platform.mouseJustPressed); i++ { down := platform.mouseJustPressed[i] || (platform.window.GetMouseButton(glfwButtonIDByIndex[i]) == glfw.Press) platform.imguiIO.SetMouseButtonDown(i, down) platform.mouseJustPressed[i] = false } platform.updateMouseCursor() } // PostRender performs a buffer swap. func (platform *GLFW) PostRender() { platform.window.SwapBuffers() } func (platform *GLFW) SetPosChangeCallback(cb func(int, int)) { platform.posChangeCallback = cb } func (platform *GLFW) SetSizeChangeCallback(cb func(int, int)) { platform.sizeChangeCallback = cb } func (platform *GLFW) Update() { glfw.PostEmptyEvent() } func (platform *GLFW) SetDropCallback(cb func(names []string)) { platform.dropCallback = cb } func (platform *GLFW) SetInputCallback(cb func(key glfw.Key, mods glfw.ModifierKey, action glfw.Action)) { platform.inputCallback = cb } func (platform *GLFW) SetCloseCallback(cb func() bool) { platform.closeCallback = cb } func (platform *GLFW) updateMouseCursor() { io := platform.imguiIO if (io.GetConfigFlags()&ConfigFlagNoMouseCursorChange) == 1 || platform.window.GetInputMode(glfw.CursorMode) == glfw.CursorDisabled { return } cursor := MouseCursor() if cursor == MouseCursorNone || io.GetMouseDrawCursor() { platform.window.SetInputMode(glfw.CursorMode, glfw.CursorHidden) } else { gCursor := platform.mouseCursors[MouseCursorArrow] if c, ok := platform.mouseCursors[cursor]; ok { gCursor = c } platform.window.SetCursor(gCursor) platform.window.SetInputMode(glfw.CursorMode, glfw.CursorNormal) } } func (platform *GLFW) setKeyMapping() { // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. platform.imguiIO.KeyMap(KeyTab, int(glfw.KeyTab)) platform.imguiIO.KeyMap(KeyLeftArrow, int(glfw.KeyLeft)) platform.imguiIO.KeyMap(KeyRightArrow, int(glfw.KeyRight)) platform.imguiIO.KeyMap(KeyUpArrow, int(glfw.KeyUp)) platform.imguiIO.KeyMap(KeyDownArrow, int(glfw.KeyDown)) platform.imguiIO.KeyMap(KeyPageUp, int(glfw.KeyPageUp)) platform.imguiIO.KeyMap(KeyPageDown, int(glfw.KeyPageDown)) platform.imguiIO.KeyMap(KeyHome, int(glfw.KeyHome)) platform.imguiIO.KeyMap(KeyEnd, int(glfw.KeyEnd)) platform.imguiIO.KeyMap(KeyInsert, int(glfw.KeyInsert)) platform.imguiIO.KeyMap(KeyDelete, int(glfw.KeyDelete)) platform.imguiIO.KeyMap(KeyBackspace, int(glfw.KeyBackspace)) platform.imguiIO.KeyMap(KeySpace, int(glfw.KeySpace)) platform.imguiIO.KeyMap(KeyEnter, int(glfw.KeyEnter)) platform.imguiIO.KeyMap(KeyEscape, int(glfw.KeyEscape)) platform.imguiIO.KeyMap(KeyA, int(glfw.KeyA)) platform.imguiIO.KeyMap(KeyC, int(glfw.KeyC)) platform.imguiIO.KeyMap(KeyV, int(glfw.KeyV)) platform.imguiIO.KeyMap(KeyX, int(glfw.KeyX)) platform.imguiIO.KeyMap(KeyY, int(glfw.KeyY)) platform.imguiIO.KeyMap(KeyZ, int(glfw.KeyZ)) } func (platform *GLFW) installCallbacks() { platform.window.SetMouseButtonCallback(platform.mouseButtonChange) platform.window.SetScrollCallback(platform.mouseScrollChange) platform.window.SetKeyCallback(platform.keyChange) platform.window.SetCharCallback(platform.charChange) platform.window.SetSizeCallback(platform.sizeChange) platform.window.SetDropCallback(platform.onDrop) platform.window.SetPosCallback(platform.posChange) platform.window.SetCloseCallback(platform.onClose) platform.window.SetFocusCallback(platform.onFocus) } var glfwButtonIndexByID = map[glfw.MouseButton]int{ glfw.MouseButton1: 0, glfw.MouseButton2: 1, glfw.MouseButton3: 2, } var glfwButtonIDByIndex = map[int]glfw.MouseButton{ 0: glfw.MouseButton1, 1: glfw.MouseButton2, 2: glfw.MouseButton3, } func (platform *GLFW) onFocus(window *glfw.Window, focused bool) { platform.imguiIO.AddFocusEvent(focused) } func (platform *GLFW) onClose(window *glfw.Window) { if platform.closeCallback != nil { shouldClose := platform.closeCallback() window.SetShouldClose(shouldClose) } } func (platform *GLFW) onDrop(window *glfw.Window, names []string) { window.Focus() if platform.dropCallback != nil { platform.dropCallback(names) } } func (platform *GLFW) posChange(window *glfw.Window, x, y int) { platform.imguiIO.SetFrameCountSinceLastInput(0) // Notfy pos changed and redraw. if platform.posChangeCallback != nil { platform.posChangeCallback(x, y) } } func (platform *GLFW) sizeChange(window *glfw.Window, width, height int) { platform.imguiIO.SetFrameCountSinceLastInput(0) // Notify size changed and redraw. if platform.sizeChangeCallback != nil { platform.sizeChangeCallback(width, height) } } func (platform *GLFW) mouseButtonChange(window *glfw.Window, rawButton glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { platform.imguiIO.SetFrameCountSinceLastInput(0) buttonIndex, known := glfwButtonIndexByID[rawButton] if known && (action == glfw.Press) { platform.mouseJustPressed[buttonIndex] = true } } func (platform *GLFW) mouseScrollChange(window *glfw.Window, x, y float64) { platform.imguiIO.SetFrameCountSinceLastInput(0) platform.imguiIO.AddMouseWheelDelta(float32(x), float32(y)) } func (platform *GLFW) keyChange(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { platform.imguiIO.SetFrameCountSinceLastInput(0) if action == glfw.Press { platform.imguiIO.KeyPress(int(key)) } if action == glfw.Release { platform.imguiIO.KeyRelease(int(key)) } // Modifiers are not reliable across systems platform.imguiIO.KeyCtrl(int(glfw.KeyLeftControl), int(glfw.KeyRightControl)) platform.imguiIO.KeyShift(int(glfw.KeyLeftShift), int(glfw.KeyRightShift)) platform.imguiIO.KeyAlt(int(glfw.KeyLeftAlt), int(glfw.KeyRightAlt)) platform.imguiIO.KeySuper(int(glfw.KeyLeftSuper), int(glfw.KeyRightSuper)) if platform.inputCallback != nil { platform.inputCallback(key, mods, action) } } func (platform *GLFW) charChange(window *glfw.Window, char rune) { platform.imguiIO.SetFrameCountSinceLastInput(0) platform.imguiIO.AddInputCharacters(string(char)) } func (platform *GLFW) GetClipboard() string { return platform.window.GetClipboardString() } func (platform *GLFW) SetClipboard(content string) { platform.window.SetClipboardString(content) } func (platform *GLFW) GetTPS() int { return platform.tps } func (platform *GLFW) SetTPS(tps int) { platform.tps = tps } // SetIcon sets the icon of the specified window. If passed an array of candidate images, // those of or closest to the sizes desired by the system are selected. If no images are // specified, the window reverts to its default icon. // // The image is ideally provided in the form of *image.NRGBA. // The pixels are 32-bit, little-endian, non-premultiplied RGBA, i.e. eight // bits per channel with the red channel first. They are arranged canonically // as packed sequential rows, starting from the top-left corner. If the image // type is not *image.NRGBA, it will be converted to it. // // The desired image sizes varies depending on platform and system settings. The selected // images will be rescaled as needed. Good sizes include 16x16, 32x32 and 48x48. func (platform *GLFW) SetIcon(icons []image.Image) { platform.window.SetIcon(icons) } // SetSizeLimits sets the size limits of the client area of the specified window. // If the window is full screen or not resizable, this function does nothing. // // The size limits are applied immediately and may cause the window to be resized. // To specify only a minimum size or only a maximum one, set the other pair to GLFW_DONT_CARE. // To disable size limits for a window, set them all to GLFW_DONT_CARE. func (platform *GLFW) SetSizeLimits(minw, minh, maxw, maxh int) { platform.window.SetSizeLimits(minw, minh, maxw, maxh) } // SetTitle sets the title of window. func (platform *GLFW) SetTitle(title string) { platform.window.SetTitle(title) } // IsMinimized checks whether window is minimized. func (platform *GLFW) IsMinimized() bool { return glfw.True == platform.window.GetAttrib(glfw.Iconified) } // IsVisible checks whether window is visible. func (platform *GLFW) IsVisible() bool { return glfw.True == platform.window.GetAttrib(glfw.Visible) }