Created
August 25, 2025 10:54
-
-
Save nakagami/09d5d50ec8c5ef06f5cc11272595b3a9 to your computer and use it in GitHub Desktop.
gio-glfw sample
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: Unlicense OR MIT | |
// GLFW doesn't build on OpenBSD and FreeBSD. | |
//go:build !openbsd && !freebsd && !android && !ios && !js | |
// +build !openbsd,!freebsd,!android,!ios,!js | |
// The glfw example demonstrates integration of Gio into a foreign | |
// windowing and rendering library, in this case GLFW | |
// (https://www.glfw.org). | |
// | |
// See the go-glfw package for installation of the native | |
// dependencies: | |
// | |
// https://github.com/go-gl/glfw | |
package main | |
import ( | |
"image" | |
"log" | |
"math" | |
"runtime" | |
"time" | |
"gioui.org/f32" | |
"gioui.org/font/gofont" | |
"gioui.org/gpu" | |
"gioui.org/io/input" | |
"gioui.org/io/pointer" | |
"gioui.org/layout" | |
"gioui.org/op" | |
"gioui.org/text" | |
"gioui.org/unit" | |
"gioui.org/widget" | |
"gioui.org/widget/material" | |
"github.com/go-gl/gl/v3.1/gles2" | |
"github.com/go-gl/gl/v3.3-core/gl" | |
"github.com/go-gl/glfw/v3.3/glfw" | |
) | |
// desktopGL is true when the (core, desktop) OpenGL should | |
// be used, false for OpenGL ES. | |
const desktopGL = runtime.GOOS == "darwin" | |
func main() { | |
// Required by the OpenGL threading model. | |
runtime.LockOSThread() | |
err := glfw.Init() | |
if err != nil { | |
log.Fatal(err) | |
} | |
defer glfw.Terminate() | |
// Gio assumes a sRGB backbuffer. | |
glfw.WindowHint(glfw.SRGBCapable, glfw.True) | |
glfw.WindowHint(glfw.ScaleToMonitor, glfw.True) | |
glfw.WindowHint(glfw.CocoaRetinaFramebuffer, glfw.True) | |
if desktopGL { | |
glfw.WindowHint(glfw.ContextVersionMajor, 3) | |
glfw.WindowHint(glfw.ContextVersionMinor, 3) | |
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) | |
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) | |
} else { | |
glfw.WindowHint(glfw.ContextCreationAPI, glfw.EGLContextAPI) | |
glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLESAPI) | |
glfw.WindowHint(glfw.ContextVersionMajor, 3) | |
glfw.WindowHint(glfw.ContextVersionMinor, 0) | |
} | |
window, err := glfw.CreateWindow(800, 600, "Gio + GLFW", nil, nil) | |
if err != nil { | |
log.Fatal(err) | |
} | |
window.MakeContextCurrent() | |
if desktopGL { | |
err = gl.Init() | |
} else { | |
err = gles2.Init() | |
} | |
if err != nil { | |
log.Fatalf("gl.Init failed: %v", err) | |
} | |
if desktopGL { | |
// Enable sRGB. | |
gl.Enable(gl.FRAMEBUFFER_SRGB) | |
// Set up default VBA, required for the forward-compatible core profile. | |
var defVBA uint32 | |
gl.GenVertexArrays(1, &defVBA) | |
gl.BindVertexArray(defVBA) | |
} | |
var queue input.Router | |
var ops op.Ops | |
th := material.NewTheme() | |
th.Shaper = text.NewShaper(text.WithCollection(gofont.Collection())) | |
gpuCtx, err := gpu.New(gpu.OpenGL{ES: !desktopGL, Shared: true}) | |
if err != nil { | |
log.Fatal(err) | |
} | |
defer gpuCtx.Release() | |
registerCallbacks(window, &queue) | |
for !window.ShouldClose() { | |
glfw.PollEvents() | |
scale, _ := window.GetContentScale() | |
width, height := window.GetFramebufferSize() | |
sz := image.Point{X: width, Y: height} | |
ops.Reset() | |
gtx := layout.Context{ | |
Ops: &ops, | |
Now: time.Now(), | |
Source: queue.Source(), | |
Metric: unit.Metric{ | |
PxPerDp: scale, | |
PxPerSp: scale, | |
}, | |
Constraints: layout.Exact(sz), | |
} | |
drawOpenGL() | |
draw(gtx, th) | |
gpuCtx.Frame(gtx.Ops, gpu.OpenGLRenderTarget{}, sz) | |
queue.Frame(gtx.Ops) | |
window.SwapBuffers() | |
} | |
} | |
var ( | |
button widget.Clickable | |
green float64 = 0.2 | |
) | |
// drawOpenGL demonstrates the direct use of OpenGL commands | |
// to draw non-Gio content below the Gio UI. | |
func drawOpenGL() { | |
if desktopGL { | |
gl.ClearColor(0, float32(green), 0, 1) | |
gl.Clear(gl.COLOR_BUFFER_BIT) | |
} else { | |
gles2.ClearColor(0, float32(green), 0, 1) | |
gles2.Clear(gl.COLOR_BUFFER_BIT) | |
} | |
} | |
// handleCursorEvent handles cursor events not processed by Gio. | |
func handleCursorEvent(xpos, ypos float64) { | |
log.Printf("mouse cursor: (%f,%f)", xpos, ypos) | |
} | |
// handleMouseButtonEvent handles mouse button events not processed by Gio. | |
func handleMouseButtonEvent(button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { | |
if action == glfw.Press { | |
green += 0.1 | |
green, _ = math.Frexp(green) | |
} | |
log.Printf("mouse button: %v action %v mods %v", button, action, mods) | |
} | |
func draw(gtx layout.Context, th *material.Theme) layout.Dimensions { | |
return layout.Center.Layout(gtx, | |
material.Button(th, &button, "Button").Layout, | |
) | |
} | |
func registerCallbacks(window *glfw.Window, q *input.Router) { | |
var btns pointer.Buttons | |
beginning := time.Now() | |
var lastPos f32.Point | |
window.SetCursorPosCallback(func(w *glfw.Window, xpos float64, ypos float64) { | |
scale := float32(1) | |
if runtime.GOOS == "darwin" { | |
// macOS cursor positions are not scaled to the underlying framebuffer | |
// size when CocoaRetinaFramebuffer is true. | |
scale, _ = w.GetContentScale() | |
} | |
lastPos = f32.Point{X: float32(xpos) * scale, Y: float32(ypos) * scale} | |
e := pointer.Event{ | |
Kind: pointer.Move, | |
Position: lastPos, | |
Source: pointer.Mouse, | |
Time: time.Since(beginning), | |
Buttons: btns, | |
} | |
q.Queue(e) | |
if _, ok := q.WakeupTime(); !ok { | |
handleCursorEvent(xpos, ypos) | |
} | |
}) | |
window.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { | |
var btn pointer.Buttons | |
switch button { | |
case glfw.MouseButton1: | |
btn = pointer.ButtonPrimary | |
case glfw.MouseButton2: | |
btn = pointer.ButtonSecondary | |
case glfw.MouseButton3: | |
btn = pointer.ButtonTertiary | |
} | |
var typ pointer.Kind | |
switch action { | |
case glfw.Release: | |
typ = pointer.Release | |
btns &^= btn | |
case glfw.Press: | |
typ = pointer.Press | |
btns |= btn | |
} | |
e := pointer.Event{ | |
Kind: typ, | |
Source: pointer.Mouse, | |
Time: time.Since(beginning), | |
Position: lastPos, | |
Buttons: btns, | |
} | |
q.Queue(e) | |
if _, ok := q.WakeupTime(); !ok { | |
handleMouseButtonEvent(button, action, mods) | |
} | |
}) | |
window.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { | |
switch action { | |
case glfw.Press: | |
log.Printf("Key pressed: %v\n", key) | |
case glfw.Release: | |
log.Printf("Key released: %v\n", key) | |
case glfw.Repeat: | |
log.Printf("Key repeated: %v\n", key) | |
} | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment