//go:build gl // 界面屏幕:主菜单与暂停菜单(UI还原.md §2:布局/控件/版本角标复刻原版)。 package ui // Button 一个按钮:区域 + 悬停高亮 + 标签纹理。 type Button struct { X, Y, W, H float32 Tex uint32 // 标签纹理(预光栅化中文) LabelW float32 LabelH float32 Action string } // TitleScreen 主菜单(UI还原.md §2:土质背景 + 标题 + 原版按钮 + 版本/版权角标)。 type TitleScreen struct { TitleTex uint32 TitleW float32 TitleH float32 SplashTex uint32 // 黄色 splash 文本(原版风格) SplashW float32 SplashH float32 BgTex uint32 // 土质背景(16px dirt 平铺预渲染) BtnTex uint32 // 原版按钮控件(widget/button.png 200×20) BtnHover uint32 // 悬停高亮(button_highlighted.png) Buttons []Button Version string // 左下角版本(原版角标) Copyright string // 右下角版权 } // 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 绘制主菜单(原版布局:背景 → 标题+splash → 按钮列 → 版本/版权角标)。 func (s *TitleScreen) Draw(r *Renderer, mx, my float32) { w, h := float32(r.w), float32(r.h) // 土质背景(UI还原.md §2 背景) if s.BgTex != 0 { r.pushQuad(quad{x: 0, y: 0, w: w, h: h, u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: s.BgTex}) } else { r.DrawRect(0, 0, w, h, [4]float32{0.08, 0.1, 0.12, 1}) } // 标题(居中偏上,原版 logo 位置) 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}) } // splash(标题右下,原版黄色倾斜文本——MVP 水平放置) if s.SplashTex != 0 { r.pushQuad(quad{x: w/2 + s.TitleW/2 - s.SplashW + 20, y: h*0.15 + s.TitleH, w: s.SplashW, h: s.SplashH, u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: s.SplashTex}) } // 按钮列(原版:居中偏下,200×20 控件 + 标签) for _, b := range s.Buttons { hover := mx >= b.X && mx <= b.X+b.W && my >= b.Y && my <= b.Y+b.H tex := s.BtnTex if hover && s.BtnHover != 0 { tex = s.BtnHover } if 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: tex}) } if b.Tex != 0 { // 标签居中 r.pushQuad(quad{x: b.X + (b.W-b.LabelW)/2, y: b.Y + (b.H-b.LabelH)/2, w: b.LabelW, h: b.LabelH, u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: b.Tex}) } } // 版本角标(左下)与版权(右下,原版样式) if s.Version != "" { r.DrawText(8, h-12, s.Version, 1, [4]float32{1, 1, 1, 1}) } if s.Copyright != "" { r.DrawText(w-r.TextWidth(s.Copyright, 1)-8, h-12, s.Copyright, 1, [4]float32{1, 1, 1, 1}) } } // PauseScreen 暂停菜单(UI还原.md §2:半透明遮罩 + 原版按钮)。 type PauseScreen struct { TitleTex uint32 TitleW float32 TitleH float32 BtnTex uint32 BtnHover uint32 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, mx, my float32) { 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 { hover := mx >= b.X && mx <= b.X+b.W && my >= b.Y && my <= b.Y+b.H tex := s.BtnTex if hover && s.BtnHover != 0 { tex = s.BtnHover } if 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: tex}) } if b.Tex != 0 { r.pushQuad(quad{x: b.X + (b.W-b.LabelW)/2, y: b.Y + (b.H-b.LabelH)/2, w: b.LabelW, h: b.LabelH, u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: b.Tex}) } } }