Improve mentions and the default Design workspace, clarify scene lighting controls, and polish editor workflows for 0.12.5-dev.
157 lines
3.9 KiB
Go
157 lines
3.9 KiB
Go
package preview
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
|
||
"image"
|
||
"image/color"
|
||
"image/draw"
|
||
"image/png"
|
||
"math"
|
||
"strings"
|
||
|
||
"aego/editor/core/code/highlight"
|
||
)
|
||
|
||
type glyph struct {
|
||
C rune
|
||
X, Y, W, H, Advance int
|
||
}
|
||
|
||
func loadFont(files interface{ ReadFile(string) ([]byte, error) }) (image.Image, map[rune]glyph) {
|
||
if files == nil {
|
||
return nil, nil
|
||
}
|
||
raw, err := files.ReadFile("engine://fonts/debug.json")
|
||
if err != nil {
|
||
return nil, nil
|
||
}
|
||
var desc struct{ Glyphs []glyph }
|
||
if json.Unmarshal(raw, &desc) != nil {
|
||
return nil, nil
|
||
}
|
||
raw, err = files.ReadFile("engine://fonts/debug.png")
|
||
if err != nil {
|
||
return nil, nil
|
||
}
|
||
im, err := png.Decode(bytes.NewReader(raw))
|
||
if err != nil {
|
||
return nil, nil
|
||
}
|
||
chars := make(map[rune]glyph, len(desc.Glyphs))
|
||
for _, g := range desc.Glyphs {
|
||
chars[g.C] = g
|
||
}
|
||
return im, chars
|
||
}
|
||
|
||
var ink = color.NRGBA{222, 230, 238, 255}
|
||
var accent = color.NRGBA{99, 172, 220, 255}
|
||
var paper = color.NRGBA{34, 38, 46, 255}
|
||
|
||
func (s *Service) label(im *image.NRGBA, x, y int, text string, c color.NRGBA) {
|
||
s.fontOnce.Do(func() {
|
||
if s.d.Engine != nil {
|
||
s.font, s.chars = loadFont(s.d.Engine.VFS())
|
||
}
|
||
})
|
||
font, chars := s.font, s.chars
|
||
if font == nil {
|
||
return
|
||
}
|
||
for _, ch := range text {
|
||
g, ok := chars[ch]
|
||
if !ok {
|
||
g = chars['?']
|
||
}
|
||
if x+g.W > im.Rect.Max.X-1 {
|
||
break
|
||
}
|
||
draw.DrawMask(im, image.Rect(x, y, x+g.W, y+g.H), image.NewUniform(c), image.Point{}, font, image.Pt(g.X, g.Y), draw.Over)
|
||
x += g.Advance
|
||
}
|
||
}
|
||
func canvas() *image.NRGBA {
|
||
im := image.NewNRGBA(image.Rect(0, 0, Size, Size))
|
||
draw.Draw(im, im.Rect, image.NewUniform(paper), image.Point{}, draw.Src)
|
||
return im
|
||
}
|
||
func raster(im *image.NRGBA) pixels { return pixels{buf: im.Pix, w: im.Rect.Dx(), h: im.Rect.Dy()} }
|
||
func (s *Service) card(header string, lines []string) pixels {
|
||
im := canvas()
|
||
s.label(im, 4, 4, strings.ToUpper(header), accent)
|
||
y := 21
|
||
for _, line := range lines {
|
||
line = strings.TrimSpace(line)
|
||
s.label(im, 4, y, line, ink)
|
||
y += 14
|
||
if y+13 > Size {
|
||
break
|
||
}
|
||
}
|
||
return raster(im)
|
||
}
|
||
|
||
// codeCard draws code as colored strokes rather than shrinking bitmap glyphs
|
||
// into illegible noise at the 48–96px sizes of the asset browser.
|
||
func codeCard(lines []string) pixels {
|
||
im := canvas()
|
||
draw.Draw(im, image.Rect(0, 0, Size, 5), image.NewUniform(accent), image.Point{}, draw.Src)
|
||
palette := [highlight.KindCount]color.NRGBA{}
|
||
for i := range palette {
|
||
palette[i] = color.NRGBA{175, 186, 197, 255}
|
||
}
|
||
palette[highlight.Keyword] = color.NRGBA{180, 145, 220, 255}
|
||
palette[highlight.Script] = color.NRGBA{180, 145, 220, 255}
|
||
palette[highlight.String] = color.NRGBA{165, 200, 140, 255}
|
||
palette[highlight.Number] = color.NRGBA{220, 165, 115, 255}
|
||
palette[highlight.Comment] = color.NRGBA{105, 130, 130, 255}
|
||
palette[highlight.Type] = color.NRGBA{115, 190, 210, 255}
|
||
var state highlight.State
|
||
var spans []highlight.Span
|
||
for lineNo, line := range lines {
|
||
y := 13 + lineNo*9
|
||
if y+3 >= Size-3 {
|
||
break
|
||
}
|
||
spans, state = highlight.Line([]byte(line), state, spans)
|
||
for _, sp := range spans {
|
||
start := -1
|
||
for i := sp.Start; i <= sp.End; i++ {
|
||
if i < sp.End && line[i] != ' ' && line[i] != '\t' {
|
||
if start < 0 {
|
||
start = i
|
||
}
|
||
continue
|
||
}
|
||
if start >= 0 {
|
||
x0, x1 := 7+start*3, min(7+i*3, Size-7)
|
||
if x0 < x1 {
|
||
draw.Draw(im, image.Rect(x0, y, x1, y+3), image.NewUniform(palette[sp.Kind]), image.Point{}, draw.Src)
|
||
}
|
||
start = -1
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return raster(im)
|
||
}
|
||
|
||
func fitPixels(im *image.NRGBA, area image.Rectangle, p pixels) {
|
||
if p.w < 1 || p.h < 1 {
|
||
return
|
||
}
|
||
scale := math.Min(float64(area.Dx())/float64(p.w), float64(area.Dy())/float64(p.h))
|
||
w, h := max(1, int(float64(p.w)*scale)), max(1, int(float64(p.h)*scale))
|
||
left := area.Min.X + (area.Dx()-w)/2
|
||
top := area.Min.Y + (area.Dy()-h)/2
|
||
for y := 0; y < h; y++ {
|
||
for x := 0; x < w; x++ {
|
||
i := (y*p.h/h*p.w + x*p.w/w) * 4
|
||
j := im.PixOffset(left+x, top+y)
|
||
copy(im.Pix[j:j+4], p.buf[i:i+4])
|
||
}
|
||
}
|
||
}
|