feat(ui): 主菜单/暂停菜单屏幕状态机——中文标题「年糕历险记」、按钮点击、Esc 暂停(烟雾测试 10 秒无崩溃)

This commit is contained in:
NianGao Dev
2026-08-15 23:43:31 +08:00
parent 272f69b6a3
commit 6cc0f12079
4 changed files with 248 additions and 3 deletions

94
internal/ui/screens.go Normal file
View File

@@ -0,0 +1,94 @@
//go:build gl
// 界面屏幕:主菜单(中文标题「年糕历险记」)与暂停菜单(UI还原.md §2)。
package ui
import (
"image"
)
// TitleScreen 主菜单屏幕(UI还原.md §2:标题 + 按钮 + 背景)。
type TitleScreen struct {
// 标题与按钮为预光栅化纹理(中文用 cjkfont、ASCII 用位图字体)
TitleTex uint32
TitleW float32
TitleH float32
Buttons []Button
}
// Button 一个按钮(点击区域 + 纹理)。
type Button struct {
X, Y, W, H float32
Tex uint32
Action string // "play" / "quit" / "resume"
}
// NewTitleScreen 创建主菜单(textures 由调用方预上传:标题与按钮标签)。
func NewTitleScreen(titleTex uint32, titleW, titleH float32, buttons []Button) *TitleScreen {
return &TitleScreen{TitleTex: titleTex, TitleW: titleW, TitleH: titleH, Buttons: buttons}
}
// HitTest 命中检测:返回点击的按钮动作。
func (s *TitleScreen) HitTest(mx, my float32) string {
for _, b := range s.Buttons {
if mx >= b.X && mx <= b.X+b.W && my >= b.Y && my <= b.Y+b.H {
return b.Action
}
}
return ""
}
// Draw 绘制主菜单(暗背景 + 标题 + 按钮,UI还原.md §2)。
func (s *TitleScreen) Draw(r *Renderer) {
w, h := float32(r.w), float32(r.h)
// 背景(暗色遮罩)
r.DrawRect(0, 0, w, h, [4]float32{0.08, 0.10, 0.12, 1})
// 标题(居中偏上)
if s.TitleTex != 0 {
r.pushQuad(quad{x: w/2 - s.TitleW/2, y: h * 0.2, w: s.TitleW, h: s.TitleH,
u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: s.TitleTex})
}
// 按钮
for _, b := range s.Buttons {
if b.Tex != 0 {
r.pushQuad(quad{x: b.X, y: b.Y, w: b.W, h: b.H,
u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: b.Tex})
}
}
}
// PauseScreen 暂停菜单(UI还原.md §2)。
type PauseScreen struct {
TitleTex uint32
TitleW float32
TitleH float32
Buttons []Button
}
// HitTest 命中检测。
func (s *PauseScreen) HitTest(mx, my float32) string {
for _, b := range s.Buttons {
if mx >= b.X && mx <= b.X+b.W && my >= b.Y && my <= b.Y+b.H {
return b.Action
}
}
return ""
}
// Draw 绘制暂停菜单(半透明遮罩 + 标题 + 按钮)。
func (s *PauseScreen) Draw(r *Renderer) {
w, h := float32(r.w), float32(r.h)
r.DrawRect(0, 0, w, h, [4]float32{0, 0, 0, 0.6})
if s.TitleTex != 0 {
r.pushQuad(quad{x: w/2 - s.TitleW/2, y: h * 0.15, w: s.TitleW, h: s.TitleH,
u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: s.TitleTex})
}
for _, b := range s.Buttons {
if b.Tex != 0 {
r.pushQuad(quad{x: b.X, y: b.Y, w: b.W, h: b.H,
u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: b.Tex})
}
}
}
var _ image.Image // 保持 image 引用