236 lines
6.4 KiB
Go
236 lines
6.4 KiB
Go
//go:build gl
|
||
|
||
// 界面屏幕:主菜单与暂停菜单(UI还原.md §2:427×240 × guiScale)。
|
||
package ui
|
||
|
||
// Button 一个按钮:区域在 427×240 虚拟空间。
|
||
type Button struct {
|
||
X, Y, W, H float32
|
||
Tex uint32
|
||
LabelW float32
|
||
LabelH float32
|
||
Action string
|
||
}
|
||
|
||
// TitleScreen 主菜单(UI还原.md §2:3D 全景背景 + 缩放控件)。
|
||
type TitleScreen struct {
|
||
TitleTex uint32
|
||
TitleW float32
|
||
TitleH float32
|
||
|
||
SplashTex uint32
|
||
SplashW float32
|
||
SplashH float32
|
||
|
||
BgTex uint32
|
||
BtnTex uint32
|
||
BtnHover uint32
|
||
Buttons []Button
|
||
|
||
Version string
|
||
Copyright string
|
||
VersionTex uint32
|
||
VersionW float32
|
||
VersionH float32
|
||
CopyrightTex uint32
|
||
CopyrightW float32
|
||
CopyrightH float32
|
||
|
||
scale, ox, oy float32
|
||
worldBg bool
|
||
}
|
||
|
||
// Layout 按窗口与整数 guiScale 放置 427×240 虚拟画布(UI还原.md §3.1)。
|
||
func (s *TitleScreen) Layout(winW, winH, scale float32) {
|
||
if scale < 1 {
|
||
scale = 1
|
||
}
|
||
s.scale = scale
|
||
vw, vh := 427*scale, 240*scale
|
||
s.ox = (winW - vw) / 2
|
||
s.oy = (winH - vh) / 2
|
||
}
|
||
|
||
func (s *TitleScreen) mapped(vx, vy, vw, vh float32) (x, y, w, h float32) {
|
||
sc := s.scale
|
||
if sc < 1 {
|
||
sc = 1
|
||
}
|
||
return s.ox + vx*sc, s.oy + vy*sc, vw * sc, vh * sc
|
||
}
|
||
|
||
// SetWorldBg 已绘制 3D 世界时只加遮罩,不再铺泥土墙。
|
||
func (s *TitleScreen) SetWorldBg(ok bool) { s.worldBg = ok }
|
||
|
||
// HitTest 命中检测(屏幕像素)。
|
||
func (s *TitleScreen) HitTest(mx, my float32) string {
|
||
for _, b := range s.Buttons {
|
||
x, y, w, h := s.mapped(b.X, b.Y, b.W, b.H)
|
||
if mx >= x && mx <= x+w && my >= y && my <= y+h {
|
||
return b.Action
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// Draw 绘制主菜单(遮罩 → 标题+splash → 按钮 → 角标)。
|
||
func (s *TitleScreen) Draw(r *Renderer, mx, my float32) {
|
||
w, h := float32(r.w), float32(r.h)
|
||
if s.worldBg {
|
||
r.DrawRect(0, 0, w, h, [4]float32{0, 0, 0, 0.38})
|
||
} else 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})
|
||
r.DrawRect(0, 0, w, h, [4]float32{0, 0, 0, 0.25})
|
||
} else {
|
||
r.DrawRect(0, 0, w, h, [4]float32{0.08, 0.1, 0.12, 1})
|
||
}
|
||
sc := s.scale
|
||
if sc < 1 {
|
||
sc = 1
|
||
}
|
||
if s.TitleTex != 0 && s.TitleH > 0 {
|
||
th := float32(42) * sc
|
||
tw := s.TitleW / s.TitleH * th
|
||
r.pushQuad(quad{x: w/2 - tw/2, y: s.oy + 16*sc, w: tw, h: th,
|
||
u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: s.TitleTex})
|
||
if s.SplashTex != 0 {
|
||
sh := float32(12) * sc
|
||
sw := s.SplashW / s.SplashH * sh
|
||
r.pushQuad(quad{x: w/2 + tw/2 - sw, y: s.oy + 16*sc + th + 2*sc, w: sw, h: sh,
|
||
u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: s.SplashTex})
|
||
}
|
||
}
|
||
for _, b := range s.Buttons {
|
||
x, y, bw, bh := s.mapped(b.X, b.Y, b.W, b.H)
|
||
hover := mx >= x && mx <= x+bw && my >= y && my <= y+bh
|
||
tex := s.BtnTex
|
||
if hover && s.BtnHover != 0 {
|
||
tex = s.BtnHover
|
||
}
|
||
if tex != 0 {
|
||
r.pushQuad(quad{x: x, y: y, w: bw, h: bh, u0: 0, v0: 0, u1: 1, v1: 1,
|
||
r: 1, g: 1, b: 1, a: 1, tex: tex})
|
||
}
|
||
if b.Tex != 0 {
|
||
lw, lh := b.LabelW*sc*0.55, b.LabelH*sc*0.55
|
||
r.pushQuad(quad{x: x + (bw-lw)/2, y: y + (bh-lh)/2, w: lw, h: lh,
|
||
u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: b.Tex})
|
||
}
|
||
}
|
||
if s.VersionTex != 0 {
|
||
r.pushQuad(quad{x: 8, y: h - s.VersionH - 8, w: s.VersionW, h: s.VersionH,
|
||
u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: s.VersionTex})
|
||
} else if s.Version != "" {
|
||
r.DrawText(8, h-12, s.Version, 1, [4]float32{1, 1, 1, 1})
|
||
}
|
||
if s.CopyrightTex != 0 {
|
||
r.pushQuad(quad{x: w - s.CopyrightW - 8, y: h - s.CopyrightH - 8, w: s.CopyrightW, h: s.CopyrightH,
|
||
u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: s.CopyrightTex})
|
||
} else 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
|
||
scale, ox, oy float32
|
||
}
|
||
|
||
// Layout 与主菜单同一套 427×240 缩放。
|
||
func (s *PauseScreen) Layout(winW, winH, scale float32) {
|
||
if scale < 1 {
|
||
scale = 1
|
||
}
|
||
s.scale = scale
|
||
vw, vh := 427*scale, 240*scale
|
||
s.ox = (winW - vw) / 2
|
||
s.oy = (winH - vh) / 2
|
||
}
|
||
|
||
func (s *PauseScreen) mapped(vx, vy, vw, vh float32) (x, y, w, h float32) {
|
||
sc := s.scale
|
||
if sc < 1 {
|
||
sc = 1
|
||
}
|
||
return s.ox + vx*sc, s.oy + vy*sc, vw * sc, vh * sc
|
||
}
|
||
|
||
// HitTest 命中检测。
|
||
func (s *PauseScreen) HitTest(mx, my float32) string {
|
||
for _, b := range s.Buttons {
|
||
x, y, w, h := s.mapped(b.X, b.Y, b.W, b.H)
|
||
if mx >= x && mx <= x+w && my >= y && my <= y+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})
|
||
sc := s.scale
|
||
if sc < 1 {
|
||
sc = 1
|
||
}
|
||
if s.TitleTex != 0 && s.TitleH > 0 {
|
||
th := float32(18) * sc
|
||
tw := s.TitleW / s.TitleH * th
|
||
r.pushQuad(quad{x: w/2 - tw/2, y: s.oy + 28*sc, w: tw, h: th,
|
||
u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: s.TitleTex})
|
||
}
|
||
for _, b := range s.Buttons {
|
||
x, y, bw, bh := s.mapped(b.X, b.Y, b.W, b.H)
|
||
hover := mx >= x && mx <= x+bw && my >= y && my <= y+bh
|
||
tex := s.BtnTex
|
||
if hover && s.BtnHover != 0 {
|
||
tex = s.BtnHover
|
||
}
|
||
if tex != 0 {
|
||
r.pushQuad(quad{x: x, y: y, w: bw, h: bh, u0: 0, v0: 0, u1: 1, v1: 1,
|
||
r: 1, g: 1, b: 1, a: 1, tex: tex})
|
||
}
|
||
if b.Tex != 0 {
|
||
lw, lh := b.LabelW*sc*0.55, b.LabelH*sc*0.55
|
||
r.pushQuad(quad{x: x + (bw-lw)/2, y: y + (bh-lh)/2, w: lw, h: lh,
|
||
u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: b.Tex})
|
||
}
|
||
}
|
||
}
|
||
|
||
// DeathScreen 死亡界面(UI还原.md §2:原因 + 重生 / 回标题;screen=11)。
|
||
type DeathScreen struct {
|
||
PauseScreen
|
||
Cause string
|
||
CauseTex uint32
|
||
CauseW float32
|
||
CauseH float32
|
||
}
|
||
|
||
func (s *DeathScreen) Draw(r *Renderer, mx, my float32) {
|
||
s.PauseScreen.Draw(r, mx, my)
|
||
sc := s.scale
|
||
if sc < 1 {
|
||
sc = 1
|
||
}
|
||
w := float32(r.w)
|
||
if s.CauseTex != 0 && s.CauseH > 0 {
|
||
th := float32(14) * sc
|
||
tw := s.CauseW / s.CauseH * th
|
||
r.pushQuad(quad{x: w/2 - tw/2, y: s.oy + 56*sc, w: tw, h: th,
|
||
u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: s.CauseTex})
|
||
return
|
||
}
|
||
if s.Cause != "" {
|
||
r.DrawText(w/2-r.TextWidth(s.Cause, 1)/2, s.oy+56*sc, s.Cause, 1, [4]float32{1, 0.7, 0.7, 1})
|
||
}
|
||
}
|