// SPDX-License-Identifier: Zlib // SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard // SPDX-FileCopyrightText: 2006-2019 Camilla Löwy // SPDX-FileCopyrightText: 2022 The Ebitengine Authors package glfwwin import ( "fmt" "math" "unsafe" ) func (w *Window) inputWindowFocus(focused bool) { if w.callbacks.focus != nil { w.callbacks.focus(w, focused) } if !focused { for key := Key(0); key <= KeyLast; key++ { if w.keys[key] == Press { scancode := platformGetKeyScancode(key) w.inputKey(key, scancode, Release, 0) } } for button := MouseButton(0); button <= MouseButtonLast; button++ { if w.mouseButtons[button] == Press { w.inputMouseClick(button, Release, 0) } } } } func (w *Window) inputWindowPos(x, y int) { if w.callbacks.pos != nil { w.callbacks.pos(w, x, y) } } func (w *Window) inputWindowSize(width, height int) { if w.callbacks.size != nil { w.callbacks.size(w, width, height) } } func (w *Window) inputWindowIconify(iconified bool) { if w.callbacks.iconify != nil { w.callbacks.iconify(w, iconified) } } func (w *Window) inputWindowMaximize(maximized bool) { if w.callbacks.maximize != nil { w.callbacks.maximize(w, maximized) } } func (w *Window) inputFramebufferSize(width, height int) { if w.callbacks.fbsize != nil { w.callbacks.fbsize(w, width, height) } } func (w *Window) inputWindowContentScale(xscale, yscale float32) { if w.callbacks.scale != nil { w.callbacks.scale(w, xscale, yscale) } } func (w *Window) inputWindowDamage() { if w.callbacks.refresh != nil { w.callbacks.refresh(w) } } func (w *Window) inputWindowCloseRequest() { w.shouldClose = true if w.callbacks.close != nil { w.callbacks.close(w) } } func (w *Window) inputWindowMonitor(monitor *Monitor) { w.monitor = monitor } func CreateWindow(width, height int, title string, monitor *Monitor, share *Window) (window *Window, ferr error) { if !_glfw.initialized { return nil, NotInitialized } if width <= 0 || height <= 0 { return nil, fmt.Errorf("glfwwin: invalid window size %dx%d: %w", width, height, InvalidValue) } fbconfig := _glfw.hints.framebuffer ctxconfig := _glfw.hints.context wndconfig := _glfw.hints.window wndconfig.width = width wndconfig.height = height wndconfig.title = title ctxconfig.share = share if err := checkValidContextConfig(&ctxconfig); err != nil { return nil, err } window = &Window{ videoMode: VidMode{ Width: width, Height: height, RedBits: fbconfig.redBits, GreenBits: fbconfig.greenBits, BlueBits: fbconfig.blueBits, RefreshRate: _glfw.hints.refreshRate, }, monitor: monitor, resizable: wndconfig.resizable, decorated: wndconfig.decorated, autoIconify: wndconfig.autoIconify, floating: wndconfig.floating, focusOnShow: wndconfig.focusOnShow, cursorMode: CursorNormal, doublebuffer: fbconfig.doublebuffer, minwidth: DontCare, minheight: DontCare, maxwidth: DontCare, maxheight: DontCare, numer: DontCare, denom: DontCare, } defer func() { if ferr != nil { window.Destroy() } }() _glfw.windows = append(_glfw.windows, window) // Open the actual window and create its context if err := window.platformCreateWindow(&wndconfig, &ctxconfig, &fbconfig); err != nil { return nil, err } return window, nil } func defaultWindowHints() error { if !_glfw.initialized { return NotInitialized } // The default is OpenGL with minimum version 1.0 _glfw.hints.context = ctxconfig{ client: OpenGLAPI, source: NativeContextAPI, major: 1, minor: 0, } // The default is a focused, visible, resizable window with decorations _glfw.hints.window = wndconfig{ resizable: true, visible: true, decorated: true, focused: true, autoIconify: true, centerCursor: true, focusOnShow: true, } // The default is 24 bits of color, 24 bits of depth and 8 bits of stencil, // double buffered _glfw.hints.framebuffer = fbconfig{ redBits: 8, greenBits: 8, blueBits: 8, alphaBits: 8, depthBits: 24, stencilBits: 8, doublebuffer: true, } // The default is to select the highest available refresh rate _glfw.hints.refreshRate = DontCare return nil } func WindowHint(hint Hint, value int) error { if !_glfw.initialized { return NotInitialized } switch hint { case RedBits: _glfw.hints.framebuffer.redBits = value case GreenBits: _glfw.hints.framebuffer.greenBits = value case BlueBits: _glfw.hints.framebuffer.blueBits = value case AlphaBits: _glfw.hints.framebuffer.alphaBits = value case DepthBits: _glfw.hints.framebuffer.depthBits = value case StencilBits: _glfw.hints.framebuffer.stencilBits = value case AccumRedBits: _glfw.hints.framebuffer.accumRedBits = value case AccumGreenBits: _glfw.hints.framebuffer.accumGreenBits = value case AccumBlueBits: _glfw.hints.framebuffer.accumBlueBits = value case AccumAlphaBits: _glfw.hints.framebuffer.accumAlphaBits = value case AuxBuffers: _glfw.hints.framebuffer.auxBuffers = value case Stereo: _glfw.hints.framebuffer.stereo = intToBool(value) case DoubleBuffer: _glfw.hints.framebuffer.doublebuffer = intToBool(value) case TransparentFramebuffer: _glfw.hints.framebuffer.transparent = intToBool(value) case Samples: _glfw.hints.framebuffer.samples = value case SRGBCapable: _glfw.hints.framebuffer.sRGB = intToBool(value) case Resizable: _glfw.hints.window.resizable = intToBool(value) case Decorated: _glfw.hints.window.decorated = intToBool(value) case Focused: _glfw.hints.window.focused = intToBool(value) case AutoIconify: _glfw.hints.window.autoIconify = intToBool(value) case Floating: _glfw.hints.window.floating = intToBool(value) case Maximized: _glfw.hints.window.maximized = intToBool(value) case Visible: _glfw.hints.window.visible = intToBool(value) case ScaleToMonitor: _glfw.hints.window.scaleToMonitor = intToBool(value) case CenterCursor: _glfw.hints.window.centerCursor = intToBool(value) case FocusOnShow: _glfw.hints.window.focusOnShow = intToBool(value) case ClientAPI: _glfw.hints.context.client = value case ContextCreationAPI: _glfw.hints.context.source = value case ContextVersionMajor: _glfw.hints.context.major = value case ContextVersionMinor: _glfw.hints.context.minor = value case ContextRobustness: _glfw.hints.context.robustness = value case OpenGLForwardCompat: _glfw.hints.context.forward = intToBool(value) case OpenGLDebugContext: _glfw.hints.context.debug = intToBool(value) case ContextNoError: _glfw.hints.context.noerror = intToBool(value) case OpenGLProfile: _glfw.hints.context.profile = value case ContextReleaseBehavior: _glfw.hints.context.release = value case RefreshRate: _glfw.hints.refreshRate = value default: return fmt.Errorf("glfwwin: invalid window hint 0x%08X: %w", hint, InvalidEnum) } return nil } // WindowHintString is not implemented. func (w *Window) Destroy() error { if !_glfw.initialized { return NotInitialized } // Allow closing of NULL (to match the behavior of free) if w == nil { return nil } // Clear all callbacks to avoid exposing a half torn-down w object // TODO: Clear w.callbacks // The w's context must not be current on another thread when the // w is destroyed current, err := _glfw.contextSlot.get() if err != nil { return err } if uintptr(unsafe.Pointer(w)) == current { if err := (*Window)(nil).MakeContextCurrent(); err != nil { return err } } for i, window := range _glfw.windows { if window == w { copy(_glfw.windows[i:], _glfw.windows[i+1:]) _glfw.windows[len(_glfw.windows)-1] = nil _glfw.windows = _glfw.windows[:len(_glfw.windows)-1] break } } w.platformDestroyWindow() return nil } func (w *Window) ShouldClose() (bool, error) { if !_glfw.initialized { return false, NotInitialized } return w.shouldClose, nil } func (w *Window) SetShouldClose(value bool) error { if !_glfw.initialized { return NotInitialized } w.shouldClose = value return nil } func (w *Window) SetTitle(title string) error { if !_glfw.initialized { return NotInitialized } if err := w.platformSetWindowTitle(title); err != nil { return err } return nil } func (w *Window) SetIcon(images []*Image) error { if !_glfw.initialized { return NotInitialized } if err := w.platformSetWindowIcon(images); err != nil { return err } return nil } func (w *Window) GetPos() (xpos, ypos int, err error) { if !_glfw.initialized { return 0, 0, NotInitialized } return w.platformGetWindowPos() } func (w *Window) SetPos(xpos, ypos int) error { if !_glfw.initialized { return NotInitialized } if w.monitor != nil { return nil } if err := w.platformSetWindowPos(xpos, ypos); err != nil { return err } return nil } func (w *Window) GetSize() (width, height int, err error) { if !_glfw.initialized { return 0, 0, NotInitialized } return w.platformGetWindowSize() } func (w *Window) SetSize(width, height int) error { if !_glfw.initialized { return NotInitialized } w.videoMode.Width = width w.videoMode.Height = height if err := w.platformSetWindowSize(width, height); err != nil { return err } return nil } func (w *Window) SetSizeLimits(minwidth, minheight, maxwidth, maxheight int) error { if !_glfw.initialized { return NotInitialized } if minwidth != DontCare && minheight != DontCare { if minwidth < 0 || minheight < 0 { return fmt.Errorf("glfwwin: invalid window minimum size %dx%d: %w", minwidth, minheight, InvalidValue) } } if maxwidth != DontCare && maxheight != DontCare { if maxwidth < 0 || maxheight < 0 || maxwidth < minwidth || maxheight < minheight { return fmt.Errorf("glfwwin: invalid window maximum size %dx%d: %w", maxwidth, maxheight, InvalidValue) } } w.minwidth = minwidth w.minheight = minheight w.maxwidth = maxwidth w.maxheight = maxheight if w.monitor != nil || !w.resizable { return nil } if err := w.platformSetWindowSizeLimits(minwidth, minheight, maxwidth, maxheight); err != nil { return err } if err := w.updateWindowStyles(); err != nil { return err } return nil } func (w *Window) SetAspectRatio(numer, denom int) error { if !_glfw.initialized { return NotInitialized } if numer != DontCare && denom != DontCare { if numer <= 0 || denom <= 0 { return fmt.Errorf("glfwwin: invalid window aspect ratio %d:%d: %w", numer, denom, InvalidValue) } } w.numer = numer w.denom = denom if w.monitor != nil || !w.resizable { return nil } if err := w.platformSetWindowAspectRatio(numer, denom); err != nil { return err } return nil } func (w *Window) GetFramebufferSize() (width, height int, err error) { if !_glfw.initialized { return 0, 0, NotInitialized } return w.platformGetFramebufferSize() } func (w *Window) GetFrameSize() (left, top, right, bottom int, err error) { if !_glfw.initialized { return 0, 0, 0, 0, NotInitialized } return w.platformGetWindowFrameSize() } func (w *Window) GetContentScale() (xscale, yscale float32, err error) { if !_glfw.initialized { return 0, 0, NotInitialized } return w.platformGetWindowContentScale() } func (w *Window) GetOpacity() (float32, error) { if !_glfw.initialized { return 0, NotInitialized } return w.platformGetWindowOpacity() } func (w *Window) SetOpacity(opacity float32) error { if !_glfw.initialized { return NotInitialized } if opacity != opacity || opacity < 0 || opacity > 1 { return fmt.Errorf("glfwwin: invalid window opacity %f: %w", opacity, InvalidValue) } if err := w.platformSetWindowOpacity(opacity); err != nil { return err } return nil } func (w *Window) Iconify() error { if !_glfw.initialized { return NotInitialized } w.platformIconifyWindow() return nil } func (w *Window) Restore() error { if !_glfw.initialized { return NotInitialized } w.platformRestoreWindow() return nil } func (w *Window) Maximize() error { if !_glfw.initialized { return NotInitialized } if w.monitor != nil { return nil } if err := w.platformMaximizeWindow(); err != nil { return err } return nil } func (w *Window) Show() error { if !_glfw.initialized { return NotInitialized } if w.monitor != nil { return nil } w.platformShowWindow() if w.focusOnShow { if err := w.platformFocusWindow(); err != nil { return err } } return nil } func (w *Window) RequestAttention() error { if !_glfw.initialized { return NotInitialized } w.platformRequestWindowAttention() return nil } func (w *Window) Hide() error { if !_glfw.initialized { return NotInitialized } if w.monitor != nil { return nil } w.platformHideWindow() return nil } func (w *Window) Focus() error { if !_glfw.initialized { return NotInitialized } if err := w.platformFocusWindow(); err != nil { return err } return nil } func (w *Window) GetAttrib(attrib Hint) (int, error) { if !_glfw.initialized { return 0, NotInitialized } switch attrib { case Focused: return boolToInt(w.platformWindowFocused()), nil case Iconified: return boolToInt(w.platformWindowIconified()), nil case Visible: return boolToInt(w.platformWindowVisible()), nil case Maximized: return boolToInt(w.platformWindowMaximized()), nil case Hovered: b, err := w.platformWindowHovered() if err != nil { return 0, err } return boolToInt(b), nil case FocusOnShow: return boolToInt(w.focusOnShow), nil case TransparentFramebuffer: return boolToInt(w.platformFramebufferTransparent()), nil case Resizable: return boolToInt(w.resizable), nil case Decorated: return boolToInt(w.decorated), nil case Floating: return boolToInt(w.floating), nil case AutoIconify: return boolToInt(w.autoIconify), nil case ClientAPI: return w.context.client, nil case ContextCreationAPI: return w.context.source, nil case ContextVersionMajor: return w.context.major, nil case ContextVersionMinor: return w.context.minor, nil case ContextRevision: return w.context.revision, nil case ContextRobustness: return w.context.robustness, nil case OpenGLForwardCompat: return boolToInt(w.context.forward), nil case OpenGLDebugContext: return boolToInt(w.context.debug), nil case OpenGLProfile: return w.context.profile, nil case ContextReleaseBehavior: return w.context.release, nil case ContextNoError: return boolToInt(w.context.noerror), nil default: return 0, fmt.Errorf("glfwwin: invalid window attribute 0x%08X: %w", attrib, InvalidEnum) } } func (w *Window) SetAttrib(attrib Hint, value int) error { if !_glfw.initialized { return NotInitialized } bValue := intToBool(value) switch attrib { case AutoIconify: w.autoIconify = bValue return nil case Resizable: if w.resizable == bValue { return nil } w.resizable = bValue if w.monitor == nil { if err := w.platformSetWindowResizable(bValue); err != nil { return nil } } return nil case Decorated: if w.decorated == bValue { return nil } w.decorated = bValue if w.monitor == nil { if err := w.platformSetWindowDecorated(bValue); err != nil { return err } } return nil case Floating: if w.floating == bValue { return nil } w.floating = bValue if w.monitor == nil { if err := w.platformSetWindowFloating(bValue); err != nil { return err } } return nil case FocusOnShow: w.focusOnShow = bValue return nil default: return fmt.Errorf("glfwwin: invalid window attribute 0x%08X: %w", attrib, InvalidEnum) } } func (w *Window) GetMonitor() (*Monitor, error) { if !_glfw.initialized { return nil, NotInitialized } return w.monitor, nil } func (w *Window) SetMonitor(monitor *Monitor, xpos, ypos, width, height, refreshRate int) error { if !_glfw.initialized { return NotInitialized } if width <= 0 || height <= 0 { return fmt.Errorf("glfwwin: invalid window size %dx%d: %w", width, height, InvalidValue) } if refreshRate < 0 && refreshRate != DontCare { return fmt.Errorf("glfwwin: invalid refresh rate %d: %w", refreshRate, InvalidValue) } w.videoMode.Width = width w.videoMode.Height = height w.videoMode.RefreshRate = refreshRate if err := w.platformSetWindowMonitor(monitor, xpos, ypos, width, height, refreshRate); err != nil { return err } return nil } func (w *Window) SetUserPointer(pointer unsafe.Pointer) error { if !_glfw.initialized { return NotInitialized } w.userPointer = pointer return nil } func (w *Window) GetUserPointer() (unsafe.Pointer, error) { if !_glfw.initialized { return nil, NotInitialized } return w.userPointer, nil } func (w *Window) SetPosCallback(cbfun PosCallback) (PosCallback, error) { if !_glfw.initialized { return nil, NotInitialized } old := w.callbacks.pos w.callbacks.pos = cbfun return old, nil } func (w *Window) SetSizeCallback(cbfun SizeCallback) (SizeCallback, error) { if !_glfw.initialized { return nil, NotInitialized } old := w.callbacks.size w.callbacks.size = cbfun return old, nil } func (w *Window) SetCloseCallback(cbfun CloseCallback) (CloseCallback, error) { if !_glfw.initialized { return nil, NotInitialized } old := w.callbacks.close w.callbacks.close = cbfun return old, nil } func (w *Window) SetRefreshCallback(cbfun RefreshCallback) (RefreshCallback, error) { if !_glfw.initialized { return nil, NotInitialized } old := w.callbacks.refresh w.callbacks.refresh = cbfun return old, nil } func (w *Window) SetFocusCallback(cbfun FocusCallback) (FocusCallback, error) { if !_glfw.initialized { return nil, NotInitialized } old := w.callbacks.focus w.callbacks.focus = cbfun return old, nil } func (w *Window) SetIconifyCallback(cbfun IconifyCallback) (IconifyCallback, error) { if !_glfw.initialized { return nil, NotInitialized } old := w.callbacks.iconify w.callbacks.iconify = cbfun return old, nil } func (w *Window) SetMaximizeCallback(cbfun MaximizeCallback) (MaximizeCallback, error) { if !_glfw.initialized { return nil, NotInitialized } old := w.callbacks.maximize w.callbacks.maximize = cbfun return old, nil } func (w *Window) SetFramebufferSizeCallback(cbfun FramebufferSizeCallback) (FramebufferSizeCallback, error) { if !_glfw.initialized { return nil, NotInitialized } old := w.callbacks.fbsize w.callbacks.fbsize = cbfun return old, nil } func (w *Window) SetContentScaleCallback(cbfun ContentScaleCallback) (ContentScaleCallback, error) { if !_glfw.initialized { return nil, NotInitialized } old := w.callbacks.scale w.callbacks.scale = cbfun return old, nil } func PollEvents() error { if !_glfw.initialized { return NotInitialized } if err := platformPollEvents(); err != nil { return err } return nil } func WaitEvents() error { if !_glfw.initialized { return NotInitialized } if err := platformWaitEvents(); err != nil { return err } return nil } func WaitEventsTimeout(timeout float64) error { if !_glfw.initialized { return NotInitialized } if timeout != timeout || timeout < 0.0 || timeout > math.MaxFloat64 { return fmt.Errorf("glfwwin: invalid time %f: %w", timeout, InvalidValue) } if err := platformWaitEventsTimeout(timeout); err != nil { return err } return nil } func PostEmptyEvent() error { if !_glfw.initialized { return NotInitialized } if err := platformPostEmptyEvent(); err != nil { return err } return nil }