//go:build gl // 工作台界面(UI还原.md §3.4、工作台与合成表.md §5): // 176×166 窗口(gui/container/crafting_table.png)+ 3×3 合成网格 (30,17) // + 输出格 (124,35) + 热键栏行 (7,141);点击槽位与手持栈交换/合并。 package ui import ( "fmt" "mc/internal/item" ) // CraftingScreen 工作台合成界面。 type CraftingScreen struct { bgTex uint32 // crafting_table.png(256×256,实际内容 176×166) items *item.Registry itemTex uint32 itemUV map[string][4]float32 // 方块物品图标回退来源(原版 block/* 图标在 blocks 图集) blockTex uint32 blockUV map[string][4]float32 // 布局(逻辑 427×240 基准,UI还原.md §3.4) winW, winH float32 // 176×166 } // NewCraftingScreen 创建工作台界面。 func NewCraftingScreen(bgTex uint32, items *item.Registry) *CraftingScreen { return &CraftingScreen{bgTex: bgTex, items: items, itemUV: map[string][4]float32{}, blockUV: map[string][4]float32{}, winW: 176, winH: 166} } // SetItemAtlas 设置物品图标纹理与 UV 表(与 HUD 共用 items 图集)。 func (s *CraftingScreen) SetItemAtlas(tex uint32, uv map[string][4]float32) { s.itemTex = tex s.itemUV = uv } // SetBlockAtlas 设置方块图集(方块物品图标回退来源)。 func (s *CraftingScreen) SetBlockAtlas(tex uint32, uv map[string][4]float32) { s.blockTex = tex s.blockUV = uv } // 合成台窗口槽位(相对窗口左上角,UI还原.md §3.4)。 var craftSlots = [10][2]float32{ {30, 17}, {48, 17}, {66, 17}, {30, 35}, {48, 35}, {66, 35}, {30, 53}, {48, 53}, {66, 53}, // 3×3 网格 {124, 35}, // 输出格 } // HitTest 命中检测:返回 "grid0".."grid8" / "result" / "hot0".."hot8" / ""。 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 hit := func(px, py float32) bool { return mx >= bx+px*scale && mx < bx+(px+18)*scale && my >= by+py*scale && my < by+(py+18)*scale } for i, p := range craftSlots { if hit(p[0], p[1]) { if i < 9 { return "grid" + string(rune('0'+i)) } return "result" } } // 热键栏行(窗口内底部,与物品栏窗口同规格) for i := 0; i < 9; i++ { if hit(7+float32(i)*18, 141) { return "hot" + string(rune('0'+i)) } } return "" } // Draw 绘制工作台界面(调用方已 Begin;mx/my 为鼠标像素坐标,held 为手持栈)。 func (s *CraftingScreen) Draw(r *Renderer, grid [9]item.Stack, result item.Stack, hasResult bool, hotbar []item.Stack, held item.Stack, 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}) // 窗口背景(纹理 256×256,内容区 176×166,UI还原.md §3.2) bx, by := w/2-s.winW/2*scale, h/2-s.winH/2*scale if s.bgTex != 0 { r.pushQuad(quad{x: bx, y: by, w: s.winW * scale, h: s.winH * scale, 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}) } // 3×3 网格 + 输出格 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]*scale+1*scale, by+p[1]*scale+1*scale, scale) } // 热键栏行 for i := 0; i < 9 && i < len(hotbar); i++ { s.drawSlot(r, hotbar[i], bx+(7+float32(i)*18)*scale+1*scale, by+141*scale+1*scale, scale) } // 手持栈跟随鼠标 if !held.Empty() { s.drawSlot(r, held, mx-8*scale, my-8*scale, scale) } } // drawSlot 在指定位置绘制物品图标(16×16 逻辑像素)。 func (s *CraftingScreen) drawSlot(r *Renderer, st item.Stack, x, y, scale float32) { if st.Empty() || s.items == nil { return } def, ok := s.items.Get(st.Name) if !ok { return } // 原版约定:block/* 图标在 blocks 图集,item/* 在 items 图集 tex := s.itemTex uv, found := s.itemUV[def.Texture] if !found { uv, found = s.blockUV[def.Texture] tex = s.blockTex } if found && tex != 0 { r.pushQuad(quad{x: x, y: y, w: 16 * scale, h: 16 * scale, u0: uv[0], v0: uv[1], u1: uv[2], v1: uv[3], r: 1, g: 1, b: 1, a: 1, tex: tex}) } if st.Count > 1 { r.DrawText(x+10*scale, y+8*scale, fmt.Sprintf("%d", st.Count), scale, [4]float32{1, 1, 1, 1}) } }