package main import ( "fmt" "os" "runtime" "strings" "github.com/go-gl/gl/v4.1-core/gl" "github.com/go-gl/glfw/v3.3/glfw" "github.com/go-gl/gltext" "github.com/go-gl/mathgl/mgl32" ) func init() { // GLFW event handling must run on the main OS thread runtime.LockOSThread() } // Position represents a 2D position type Position struct { X, Y float32 } // Size represents the width and height of a UI element type Size struct { Width, Height float32 } // EventType represents the type of an event type EventType int const ( EventTypeClick EventType = iota EventTypeMouseMove EventTypeHover ) // Event represents an input event type Event struct { Type EventType Position Position } // UIElement defines the interface for UI elements type UIElement interface { Draw() Update() HandleEvent(event Event) } // Container defines the interface for UI containers type Container interface { UIElement AddChild(UIElement) RemoveChild(UIElement) } // Context manages the OpenGL context and resources type Context struct { shaderProgram uint32 font *gltext.Font windowWidth int windowHeight int } func NewContext(windowWidth, windowHeight int) *Context { return &Context{ windowWidth: windowWidth, windowHeight: windowHeight, } } func (c *Context) InitOpenGL() { if err := gl.Init(); err != nil { panic(err) } version := gl.GoStr(gl.GetString(gl.VERSION)) fmt.Println("OpenGL version", version) // Initialize shader program c.shaderProgram = NewShaderProgram(vertexShaderSource, fragmentShaderSource) // Load font using gltext fontData, err := os.Open("arial.ttf") if err != nil { panic(err) } defer fontData.Close() // Load the truetype font: specify scale, rune range (ASCII), and direction font, err := gltext.LoadTruetype(fontData, 24, 32, 127, gltext.LeftToRight) if err != nil { panic(err) } c.font = font } func (c *Context) Clear() { gl.ClearColor(0.0, 0.0, 0.0, 1.0) gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) } func (c *Context) SwapBuffers(window *glfw.Window) { window.SwapBuffers() } // Window represents the main application window type Window struct { width int height int title string transparent bool panels []*Panel glContext *Context glfwWindow *glfw.Window } func NewWindow(width, height int, title string) *Window { if err := glfw.Init(); err != nil { panic(err) } glfw.WindowHint(glfw.ContextVersionMajor, 4) glfw.WindowHint(glfw.ContextVersionMinor, 1) glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) glfwWindow, err := glfw.CreateWindow(width, height, title, nil, nil) if err != nil { panic(err) } glfwWindow.MakeContextCurrent() return &Window{ width: width, height: height, title: title, panels: []*Panel{}, glfwWindow: glfwWindow, } } func (w *Window) SetTransparent(t bool) { w.transparent = t // Apply transparency settings if needed } func (w *Window) DefineContext() *Context { w.glContext = NewContext(w.width, w.height) w.glContext.InitOpenGL() return w.glContext } func (w *Window) CreatePanel() *Panel { panel := NewPanel() w.panels = append(w.panels, panel) return panel } func (w *Window) registerCallbacks() { w.glfwWindow.SetCursorPosCallback(w.cursorPosCallback) w.glfwWindow.SetMouseButtonCallback(w.mouseButtonCallback) } func (w *Window) cursorPosCallback(window *glfw.Window, xpos float64, ypos float64) { event := Event{ Type: EventTypeMouseMove, Position: Position{X: float32(xpos), Y: float32(ypos)}, } w.handleEvent(event) } func (w *Window) mouseButtonCallback(window *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { if button == glfw.MouseButtonLeft && action == glfw.Press { xpos, ypos := window.GetCursorPos() event := Event{ Type: EventTypeClick, Position: Position{X: float32(xpos), Y: float32(ypos)}, } w.handleEvent(event) } } func (w *Window) handleEvent(event Event) { for _, panel := range w.panels { panel.HandleEvent(event) } } func (w *Window) update() { for _, panel := range w.panels { panel.Update() } } func (w *Window) draw() { for _, panel := range w.panels { panel.Draw() } } func (w *Window) Start() { w.registerCallbacks() for !w.glfwWindow.ShouldClose() { glfw.PollEvents() w.glContext.Clear() w.update() w.draw() w.glContext.SwapBuffers(w.glfwWindow) } glfw.Terminate() } // Panel is a container for UI elements type Panel struct { children []UIElement } func NewPanel() *Panel { return &Panel{ children: []UIElement{}, } } func (p *Panel) AddChild(e UIElement) { p.children = append(p.children, e) } func (p *Panel) RemoveChild(e UIElement) { // Implement child removal logic if needed } func (p *Panel) Draw() { for _, child := range p.children { child.Draw() } } func (p *Panel) Update() { for _, child := range p.children { child.Update() } } func (p *Panel) HandleEvent(event Event) { for _, child := range p.children { child.HandleEvent(event) } } // Button is a clickable UI element type Button struct { label string onClick func() Position Size isHovered bool context *Context } func NewButton(label string, onClick func(), context *Context) *Button { return &Button{ label: label, onClick: onClick, context: context, Position: Position{X: 0, Y: 0}, Size: Size{Width: 100, Height: 50}, isHovered: false, } } func (b *Button) contains(pos Position) bool { return pos.X >= b.Position.X && pos.X <= b.Position.X+b.Size.Width && pos.Y >= b.Position.Y && pos.Y <= b.Position.Y+b.Size.Height } func (b *Button) Draw() { var color [4]float32 if b.isHovered { color = [4]float32{0.7, 0.7, 0.7, 1.0} // Lighter when hovered } else { color = [4]float32{0.5, 0.5, 0.5, 1.0} } rect := NewRectangle(b.Position.X, b.Position.Y, b.Size.Width, b.Size.Height, color, b.context) rect.Draw() rect.Destroy() // Draw the label x := b.Position.X + 10 y := b.Position.Y + b.Size.Height/2 + 8 // Approximate vertical centering b.context.font.Printf(x, y, b.label) } func (b *Button) Update() { // Update the button if needed } func (b *Button) HandleEvent(event Event) { switch event.Type { case EventTypeClick: if b.contains(event.Position) { b.onClick() } case EventTypeMouseMove: if b.contains(event.Position) { if !b.isHovered { b.isHovered = true } } else { if b.isHovered { b.isHovered = false } } } } // Rectangle is used to draw a rectangle using OpenGL type Rectangle struct { Position Size Color [4]float32 vao uint32 vbo uint32 shaderProgram uint32 context *Context } func NewRectangle(x, y, width, height float32, color [4]float32, context *Context) *Rectangle { rect := &Rectangle{ Position: Position{X: x, Y: y}, Size: Size{Width: width, Height: height}, Color: color, shaderProgram: context.shaderProgram, context: context, } rect.initOpenGL() return rect } func (r *Rectangle) initOpenGL() { vertices := []float32{ r.Position.X, r.Position.Y, 0.0, r.Position.X + r.Size.Width, r.Position.Y, 0.0, r.Position.X + r.Size.Width, r.Position.Y + r.Size.Height, 0.0, r.Position.X, r.Position.Y + r.Size.Height, 0.0, } indices := []uint32{ 0, 1, 2, 2, 3, 0, } gl.GenVertexArrays(1, &r.vao) gl.BindVertexArray(r.vao) gl.GenBuffers(1, &r.vbo) gl.BindBuffer(gl.ARRAY_BUFFER, r.vbo) gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW) var ebo uint32 gl.GenBuffers(1, &ebo) gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo) gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(indices)*4, gl.Ptr(indices), gl.STATIC_DRAW) gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 3*4, gl.PtrOffset(0)) gl.EnableVertexAttribArray(0) gl.BindVertexArray(0) } func (r *Rectangle) Draw() { gl.UseProgram(r.shaderProgram) // Set uniforms model := mgl32.Ident4() projection := mgl32.Ortho2D(0, float32(r.context.windowWidth), float32(r.context.windowHeight), 0) modelLoc := gl.GetUniformLocation(r.shaderProgram, gl.Str("model\x00")) projectionLoc := gl.GetUniformLocation(r.shaderProgram, gl.Str("projection\x00")) colorLoc := gl.GetUniformLocation(r.shaderProgram, gl.Str("color\x00")) gl.UniformMatrix4fv(modelLoc, 1, false, &model[0]) gl.UniformMatrix4fv(projectionLoc, 1, false, &projection[0]) gl.Uniform4fv(colorLoc, 1, &r.Color[0]) gl.BindVertexArray(r.vao) gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, gl.PtrOffset(0)) gl.BindVertexArray(0) } func (r *Rectangle) Destroy() { gl.DeleteVertexArrays(1, &r.vao) gl.DeleteBuffers(1, &r.vbo) } // Shader sources var vertexShaderSource = ` #version 410 core layout(location = 0) in vec3 position; uniform mat4 model; uniform mat4 projection; void main() { gl_Position = projection * model * vec4(position, 1.0); } ` + "\x00" var fragmentShaderSource = ` #version 410 core out vec4 fragColor; uniform vec4 color; void main() { fragColor = color; } ` + "\x00" // NewShaderProgram compiles and links the vertex and fragment shaders func NewShaderProgram(vertexShaderSource, fragmentShaderSource string) uint32 { vertexShader := gl.CreateShader(gl.VERTEX_SHADER) cvertexShaderSource, freeVertex := gl.Strs(vertexShaderSource) gl.ShaderSource(vertexShader, 1, cvertexShaderSource, nil) freeVertex() gl.CompileShader(vertexShader) var status int32 gl.GetShaderiv(vertexShader, gl.COMPILE_STATUS, &status) if status == gl.FALSE { var logLength int32 gl.GetShaderiv(vertexShader, gl.INFO_LOG_LENGTH, &logLength) log := strings.Repeat("\x00", int(logLength+1)) gl.GetShaderInfoLog(vertexShader, logLength, nil, gl.Str(log)) panic(fmt.Sprintf("failed to compile vertex shader: %v", log)) } fragmentShader := gl.CreateShader(gl.FRAGMENT_SHADER) cfragmentShaderSource, freeFragment := gl.Strs(fragmentShaderSource) gl.ShaderSource(fragmentShader, 1, cfragmentShaderSource, nil) freeFragment() gl.CompileShader(fragmentShader) gl.GetShaderiv(fragmentShader, gl.COMPILE_STATUS, &status) if status == gl.FALSE { var logLength int32 gl.GetShaderiv(fragmentShader, gl.INFO_LOG_LENGTH, &logLength) log := strings.Repeat("\x00", int(logLength+1)) gl.GetShaderInfoLog(fragmentShader, logLength, nil, gl.Str(log)) panic(fmt.Sprintf("failed to compile fragment shader: %v", log)) } program := gl.CreateProgram() gl.AttachShader(program, vertexShader) gl.AttachShader(program, fragmentShader) gl.LinkProgram(program) gl.GetProgramiv(program, gl.LINK_STATUS, &status) if status == gl.FALSE { var logLength int32 gl.GetProgramiv(program, gl.INFO_LOG_LENGTH, &logLength) log := strings.Repeat("\x00", int(logLength+1)) gl.GetProgramInfoLog(program, logLength, nil, gl.Str(log)) panic(fmt.Sprintf("failed to link program: %v", log)) } gl.DeleteShader(vertexShader) gl.DeleteShader(fragmentShader) return program } func main() { window := NewWindow(800, 600, "Demo Window") window.SetTransparent(true) context := window.DefineContext() panel := window.CreatePanel() button := NewButton("Click me!", func() { fmt.Println("Button pressed!") }, context) button.Position = Position{X: 100, Y: 100} button.Size = Size{Width: 200, Height: 50} panel.AddChild(button) window.Start() }