// Package page provides the interface for pages package page import ( "fmt" "github.com/go-gl/gl/v3.3-core/gl" "github.com/go-gl/mathgl/mgl32" "github.com/veandco/go-sdl2/sdl" "rhemawavetech.com/rhemawave/internal/shaderManager" ) // Page defines the contract for all application screens. type Page interface { Init(ctx *PageCtx) error HandleEvent(event *sdl.Event) error Update(dt float32) error Render() error Destroy() error } // Base is a helper struct that pages can embed to get common functionality. type Base struct { Context *PageCtx QuadVAO uint32 QuadVBO uint32 Projection mgl32.Mat4 ScreenWidth int32 ScreenHeight int32 ScreenCenterX int32 ScreenCenterY int32 } // Init stores the application context and sets up common resources. func (p *Base) Init(ctx *PageCtx) error { p.Context = ctx vertices := []float32{ // pos(x,y) tex(u,v) 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, } W := p.Context.W H := p.Context.H p.Projection = mgl32.Ortho(0, float32(W), 0, float32(H), -1, 1) p.ScreenHeight = H p.ScreenWidth = W p.ScreenCenterY = H / 2 p.ScreenCenterX = W / 2 gl.GenVertexArrays(1, &p.QuadVAO) gl.GenBuffers(1, &p.QuadVBO) gl.BindVertexArray(p.QuadVAO) gl.BindBuffer(gl.ARRAY_BUFFER, p.QuadVBO) gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW) stride := int32(4 * 4) // Each vertex has 4 floats (pos.x, pos.y, tex.u, tex.v) // Attribute 0: Vertex Position (vec2) gl.EnableVertexAttribArray(0) gl.VertexAttribPointerWithOffset(0, 2, gl.FLOAT, false, stride, 0) // Attribute 1: Texture Coordinate (vec2) gl.EnableVertexAttribArray(1) gl.VertexAttribPointerWithOffset(1, 2, gl.FLOAT, false, stride, 2*4) // Offset by 2 floats gl.BindBuffer(gl.ARRAY_BUFFER, 0) gl.BindVertexArray(0) gl.Disable(gl.BLEND) gl.UseProgram(0) return nil } // Destroy cleans up the common OpenGL resources. func (p *Base) Destroy() error { gl.DeleteBuffers(1, &p.QuadVBO) gl.DeleteVertexArrays(1, &p.QuadVAO) return nil } // BeginTextureRendering sets up shared GL state for a batch of RenderTexture calls. // Call this once before a series of RenderTexture calls, then EndTextureRendering after. func (p *Base) BeginTextureRendering(shader *shaderManager.Shader) { gl.UseProgram(shader.ProgramID) gl.Uniform1i(shader.LocTexture, 0) gl.ActiveTexture(gl.TEXTURE0) gl.Enable(gl.BLEND) gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) gl.BindVertexArray(p.QuadVAO) } // EndTextureRendering restores GL state after a batch of RenderTexture calls. func (p *Base) EndTextureRendering() { gl.BindVertexArray(0) gl.Disable(gl.BLEND) gl.UseProgram(0) } // RenderTexture draws a given texture to the screen at a specified position and size. // For best performance, wrap multiple calls with BeginTextureRendering/EndTextureRendering. // This method also works standalone for one-off renders. func (p *Base) RenderTexture(shader *shaderManager.Shader, textureID uint32, position, size mgl32.Vec2, color sdl.Color) error { gl.UseProgram(shader.ProgramID) gl.Uniform1i(shader.LocTexture, 0) if shader.LocTextColor == -1 { return fmt.Errorf("could not find 'u_textColor' uniform in shader program %d", shader.ProgramID) } gl.Uniform4f(shader.LocTextColor, float32(color.R)/255.0, float32(color.G)/255.0, float32(color.B)/255.0, float32(color.A)/255.0) model := mgl32.Ident4() model = model.Mul4(mgl32.Translate3D(position.X(), position.Y(), 0)) model = model.Mul4(mgl32.Scale3D(size.X(), size.Y(), 1)) mvp := p.Projection.Mul4(model) gl.UniformMatrix4fv(shader.LocMVPMatrix, 1, false, &mvp[0]) gl.ActiveTexture(gl.TEXTURE0) gl.BindTexture(gl.TEXTURE_2D, textureID) gl.Enable(gl.BLEND) gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) gl.BindVertexArray(p.QuadVAO) gl.DrawArrays(gl.TRIANGLES, 0, 6) gl.BindVertexArray(0) gl.Disable(gl.BLEND) gl.UseProgram(0) return nil } // RenderTextureBatched is a lightweight version of RenderTexture for use inside // a BeginTextureRendering/EndTextureRendering block. It skips redundant state setup. func (p *Base) RenderTextureBatched(shader *shaderManager.Shader, textureID uint32, position, size mgl32.Vec2, color sdl.Color) { gl.Uniform4f(shader.LocTextColor, float32(color.R)/255.0, float32(color.G)/255.0, float32(color.B)/255.0, float32(color.A)/255.0) model := mgl32.Ident4() model = model.Mul4(mgl32.Translate3D(position.X(), position.Y(), 0)) model = model.Mul4(mgl32.Scale3D(size.X(), size.Y(), 1)) mvp := p.Projection.Mul4(model) gl.UniformMatrix4fv(shader.LocMVPMatrix, 1, false, &mvp[0]) gl.BindTexture(gl.TEXTURE_2D, textureID) gl.DrawArrays(gl.TRIANGLES, 0, 6) } // RenderSolidColorQuad draws a rectangle of a solid color to the screen. func (p *Base) RenderSolidColorQuad(shader *shaderManager.Shader, position, size mgl32.Vec2, color sdl.Color) error { gl.UseProgram(shader.ProgramID) model := mgl32.Ident4() model = model.Mul4(mgl32.Translate3D(position.X(), position.Y(), 0)) model = model.Mul4(mgl32.Scale3D(size.X(), size.Y(), 1)) mvp := p.Projection.Mul4(model) if shader.LocMVPMatrix == -1 { return fmt.Errorf("RenderSolidColorQuad: could not find 'u_mvpMatrix' uniform") } gl.UniformMatrix4fv(shader.LocMVPMatrix, 1, false, &mvp[0]) if shader.LocColor == -1 { return fmt.Errorf("RenderSolidColorQuad: could not find 'u_color' uniform") } gl.Uniform4f(shader.LocColor, float32(color.R)/255.0, float32(color.G)/255.0, float32(color.B)/255.0, float32(color.A)/255.0) gl.Enable(gl.BLEND) gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) gl.BindVertexArray(p.QuadVAO) gl.DrawArrays(gl.TRIANGLES, 0, 6) gl.BindVertexArray(0) gl.Disable(gl.BLEND) gl.UseProgram(0) return nil } // Provide default implementations for the Page interface. func (p *Base) HandleEvent(event sdl.Event) error { return nil } func (p *Base) Update(dt float32) error { return nil } func (p *Base) Render() error { return nil } // --- Helper Functions --- // keycodeToChar converts an SDL keycode to a rune, respecting SHIFT state. // Returns 0 for non-printable keys. func keycodeToChar(sym sdl.Keycode, mod uint16) rune { shifted := mod&sdl.KMOD_SHIFT != 0 // Letters if sym >= sdl.K_a && sym <= sdl.K_z { ch := rune(sym) if shifted { ch -= 'a' - 'A' } return ch } // Digits and their shifted symbols if sym >= sdl.K_0 && sym <= sdl.K_9 { if shifted { shiftDigits := []rune{')', '!', '@', '#', '$', '%', '^', '&', '*', '('} return shiftDigits[sym-sdl.K_0] } return rune(sym) } // Common punctuation / symbols type charPair struct{ normal, shifted rune } punctuation := map[sdl.Keycode]charPair{ sdl.K_SPACE: {' ', ' '}, sdl.K_MINUS: {'-', '_'}, sdl.K_EQUALS: {'=', '+'}, sdl.K_LEFTBRACKET: {'[', '{'}, sdl.K_RIGHTBRACKET: {']', '}'}, sdl.K_BACKSLASH: {'\\', '|'}, sdl.K_SEMICOLON: {';', ':'}, sdl.K_QUOTE: {'\'', '"'}, sdl.K_BACKQUOTE: {'`', '~'}, sdl.K_COMMA: {',', '<'}, sdl.K_PERIOD: {'.', '>'}, sdl.K_SLASH: {'/', '?'}, } if pair, ok := punctuation[sym]; ok { if shifted { return pair.shifted } return pair.normal } return 0 }