216 lines
5.8 KiB
Go
216 lines
5.8 KiB
Go
//go:build gl
|
||
|
||
// 工作台界面(UI还原.md §3.4、工作台与合成表.md §5):
|
||
// 176×166 窗口 + 3×3 (30,17) + 输出 (124,35) + 主区 (7,83) + 热键栏 (7,141)
|
||
// + 右侧最小配方书。
|
||
package ui
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
|
||
"mc/internal/item"
|
||
"mc/internal/recipe"
|
||
)
|
||
|
||
// CraftingScreen 工作台合成界面。
|
||
type CraftingScreen struct {
|
||
itemIcons
|
||
bgTex uint32
|
||
winW, winH float32
|
||
BookOpen bool
|
||
BookQuery string
|
||
BookScroll int
|
||
OpenT float32
|
||
Labels map[string]string
|
||
|
||
tipTex uint32
|
||
tipW, tipH float32
|
||
}
|
||
|
||
// NewCraftingScreen 创建工作台界面。
|
||
func NewCraftingScreen(bgTex uint32, items *item.Registry) *CraftingScreen {
|
||
s := &CraftingScreen{bgTex: bgTex, winW: 176, winH: 166, OpenT: 1}
|
||
s.items = items
|
||
s.itemUV = map[string][4]float32{}
|
||
s.blockUV = map[string][4]float32{}
|
||
return s
|
||
}
|
||
|
||
// SetItemAtlas 设置物品图标纹理与 UV 表。
|
||
func (s *CraftingScreen) SetItemAtlas(tex uint32, uv map[string][4]float32) {
|
||
s.setItemAtlas(tex, uv)
|
||
}
|
||
|
||
// SetBlockAtlas 设置方块图集。
|
||
func (s *CraftingScreen) SetBlockAtlas(tex uint32, uv map[string][4]float32) {
|
||
s.setBlockAtlas(tex, uv)
|
||
}
|
||
|
||
// SetTooltip 设置悬停名称纹理。
|
||
func (s *CraftingScreen) SetTooltip(tex uint32, w, h float32) {
|
||
s.tipTex = tex
|
||
s.tipW, s.tipH = w, h
|
||
}
|
||
|
||
var craftSlots = [10][2]float32{
|
||
{30, 17}, {48, 17}, {66, 17},
|
||
{30, 35}, {48, 35}, {66, 35},
|
||
{30, 53}, {48, 53}, {66, 53},
|
||
{124, 35},
|
||
}
|
||
|
||
// HitTest 命中:grid0–8 / result / main0–26 / hot0–8 / book / recipeN。
|
||
func (s *CraftingScreen) HitTest(mx, my float32, screenW, screenH, scale float32) string {
|
||
bx, by := screenW/2-s.winW/2*scale, screenH/2-s.winH/2*scale
|
||
for i, p := range craftSlots {
|
||
if hitSlot(mx, my, bx, by, p[0], p[1], scale) {
|
||
if i < 9 {
|
||
return "grid" + string(rune('0'+i))
|
||
}
|
||
return "result"
|
||
}
|
||
}
|
||
for i := 0; i < 27; i++ {
|
||
px := 7 + float32(i%9)*18
|
||
py := 83 + float32(i/9)*18
|
||
if hitSlot(mx, my, bx, by, px, py, scale) {
|
||
return "main" + fmt.Sprintf("%d", i)
|
||
}
|
||
}
|
||
for i := 0; i < 9; i++ {
|
||
if hitSlot(mx, my, bx, by, 7+float32(i)*18, 141, scale) {
|
||
return "hot" + fmt.Sprintf("%d", i)
|
||
}
|
||
}
|
||
// 配方书按钮:窗口右侧
|
||
if mx >= bx+(s.winW+2)*scale && mx < bx+(s.winW+20)*scale &&
|
||
my >= by+8*scale && my < by+26*scale {
|
||
return "book"
|
||
}
|
||
if s.BookOpen {
|
||
if mx >= bx+(s.winW+22)*scale && mx < bx+(s.winW+100)*scale &&
|
||
my >= by && my < by+16*scale {
|
||
return "search"
|
||
}
|
||
for i := 0; i < 8; i++ {
|
||
px := s.winW + 22
|
||
py := 20 + float32(i)*16
|
||
if hitSlot(mx, my, bx, by, px, py, scale) {
|
||
return "recipe" + fmt.Sprintf("%d", s.BookScroll+i)
|
||
}
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// FilterRecipes 按搜索过滤。
|
||
func FilterRecipes(all []*recipe.Recipe, q string, labels map[string]string) []*recipe.Recipe {
|
||
q = strings.ToLower(strings.TrimSpace(q))
|
||
if q == "" {
|
||
return all
|
||
}
|
||
var out []*recipe.Recipe
|
||
for _, rc := range all {
|
||
if rc == nil {
|
||
continue
|
||
}
|
||
if strings.Contains(strings.ToLower(rc.Result.Item), q) {
|
||
out = append(out, rc)
|
||
continue
|
||
}
|
||
if labels != nil && strings.Contains(strings.ToLower(labels[rc.Result.Item]), q) {
|
||
out = append(out, rc)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// Draw 绘制工作台(slots 为完整背包;recipes 为配方书条目)。
|
||
func (s *CraftingScreen) Draw(r *Renderer, grid [9]item.Stack, result item.Stack, hasResult bool, slots []item.Stack, held item.Stack, recipes []*recipe.Recipe, unlocked map[string]bool, mx, my, scale float32) {
|
||
w, h := float32(r.w), float32(r.h)
|
||
r.DrawRect(0, 0, w, h, [4]float32{0, 0, 0, 0.35})
|
||
sc := scale * (0.85 + 0.15*s.OpenT)
|
||
bx, by := w/2-s.winW/2*sc, h/2-s.winH/2*sc
|
||
if s.bgTex != 0 {
|
||
r.pushQuad(quad{x: bx, y: by, w: s.winW * sc, h: s.winH * sc,
|
||
u0: 0, v0: 0, u1: 176.0 / 256.0, v1: 166.0 / 256.0,
|
||
r: 1, g: 1, b: 1, a: 1, tex: s.bgTex})
|
||
}
|
||
for i, p := range craftSlots {
|
||
var st item.Stack
|
||
if i < 9 {
|
||
st = grid[i]
|
||
} else if hasResult {
|
||
st = result
|
||
}
|
||
s.drawSlot(r, st, bx+p[0]*sc+1*sc, by+p[1]*sc+1*sc, sc)
|
||
}
|
||
for i := 0; i < 27; i++ {
|
||
idx := 9 + i
|
||
st := item.Stack{}
|
||
if idx < len(slots) {
|
||
st = slots[idx]
|
||
}
|
||
px := 7 + float32(i%9)*18
|
||
py := 83 + float32(i/9)*18
|
||
s.drawSlot(r, st, bx+(px+1)*sc, by+(py+1)*sc, sc)
|
||
}
|
||
for i := 0; i < 9 && i < len(slots); i++ {
|
||
s.drawSlot(r, slots[i], bx+(7+float32(i)*18)*sc+1*sc, by+141*sc+1*sc, sc)
|
||
}
|
||
btn := [4]float32{0.55, 0.45, 0.25, 1}
|
||
if s.BookOpen {
|
||
btn = [4]float32{0.75, 0.65, 0.35, 1}
|
||
}
|
||
r.DrawRect(bx+(s.winW+2)*sc, by+8*sc, 18*sc, 18*sc, btn)
|
||
r.DrawText(bx+(s.winW+6)*sc, by+13*sc, "R", sc, [4]float32{1, 1, 0.85, 1})
|
||
if s.BookOpen {
|
||
r.DrawRect(bx+(s.winW+20)*sc, by*1, 102*sc, 160*sc, [4]float32{0.12, 0.12, 0.12, 0.88})
|
||
r.DrawRect(bx+(s.winW+22)*sc, by+2*sc, 78*sc, 14*sc, [4]float32{0, 0, 0, 0.55})
|
||
qs := s.BookQuery
|
||
if qs == "" {
|
||
qs = "search"
|
||
}
|
||
r.DrawText(bx+(s.winW+24)*sc, by+5*sc, qs, 0.65*sc, [4]float32{0.85, 0.85, 0.85, 1})
|
||
vis := FilterRecipes(recipes, s.BookQuery, s.Labels)
|
||
if s.BookScroll < 0 {
|
||
s.BookScroll = 0
|
||
}
|
||
n := 8
|
||
if len(vis)-s.BookScroll < n {
|
||
n = len(vis) - s.BookScroll
|
||
}
|
||
if n < 0 {
|
||
n = 0
|
||
}
|
||
for i := 0; i < n; i++ {
|
||
rc := vis[s.BookScroll+i]
|
||
py := 20 + float32(i)*16
|
||
st := item.Stack{Name: rc.Result.Item, Count: uint8(min(rc.Result.Count, 127))}
|
||
s.drawSlot(r, st, bx+(s.winW+23)*sc, by+(py+1)*sc, sc)
|
||
col := [4]float32{1, 1, 1, 1}
|
||
if unlocked != nil && !unlocked[rc.Result.Item] {
|
||
col = [4]float32{0.45, 0.45, 0.45, 1}
|
||
}
|
||
r.DrawText(bx+(s.winW+42)*sc, by+(py+4)*sc, rc.Result.Item, 0.6*sc, col)
|
||
}
|
||
}
|
||
if !held.Empty() {
|
||
s.drawSlot(r, held, mx-8*scale, my-8*scale, scale)
|
||
}
|
||
if s.tipTex != 0 && s.tipW > 0 {
|
||
tx, ty := mx+12, my+12
|
||
drawTooltipBG(r, tx, ty, s.tipW, s.tipH)
|
||
r.pushQuad(quad{x: tx, y: ty, w: s.tipW, h: s.tipH,
|
||
u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: s.tipTex})
|
||
}
|
||
}
|
||
|
||
func min(a, b int) int {
|
||
if a < b {
|
||
return a
|
||
}
|
||
return b
|
||
}
|