Lists, tables, trees, grids, inventories and records in data/. Outline and GridView build on ui.List: ui.Outline strokes its arrows and ui.GridView spaces and tints items. The pixel theme keeps MyGo's row highlights clear so scrolled rows stay on whole dots; off lists take a veil against the wheel. Adds Canvas.Silhouette, SwitchOptions Label and exports Rarity.Slot.
122 lines
2.6 KiB
Go
122 lines
2.6 KiB
Go
package pixel
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Sprite is hand-placed art in palette slots, in frames.
|
|
type Sprite struct {
|
|
W, H int
|
|
frames [][]int8
|
|
}
|
|
|
|
const (
|
|
cellClear int8 = -1
|
|
cellInk int8 = -2
|
|
)
|
|
|
|
// Art letters naming slots; '.' is clear, '#' the ink.
|
|
const artSlots = "kbflsenwaAigyrcm"
|
|
|
|
// MustSprite parses string art; blank lines split frames.
|
|
func MustSprite(art string) *Sprite {
|
|
var frames [][]string
|
|
var cur []string
|
|
for _, line := range strings.Split(strings.TrimSpace(art), "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
if cur != nil {
|
|
frames, cur = append(frames, cur), nil
|
|
}
|
|
continue
|
|
}
|
|
cur = append(cur, line)
|
|
}
|
|
if cur == nil {
|
|
panic("pixel: empty sprite")
|
|
}
|
|
frames = append(frames, cur)
|
|
sp := &Sprite{W: len(frames[0][0]), H: len(frames[0])}
|
|
for _, f := range frames {
|
|
if len(f) != sp.H {
|
|
panic(fmt.Sprintf("pixel: sprite frames of %d and %d rows", sp.H, len(f)))
|
|
}
|
|
px := make([]int8, 0, sp.W*sp.H)
|
|
for _, row := range f {
|
|
if len(row) != sp.W {
|
|
panic(fmt.Sprintf("pixel: sprite row %q is not %d wide", row, sp.W))
|
|
}
|
|
for i := range len(row) {
|
|
px = append(px, cell(row[i]))
|
|
}
|
|
}
|
|
sp.frames = append(sp.frames, px)
|
|
}
|
|
return sp
|
|
}
|
|
|
|
func cell(c byte) int8 {
|
|
switch c {
|
|
case '.':
|
|
return cellClear
|
|
case '#':
|
|
return cellInk
|
|
}
|
|
if i := strings.IndexByte(artSlots, c); i >= 0 {
|
|
return int8(i)
|
|
}
|
|
panic(fmt.Sprintf("pixel: sprite character %q", c))
|
|
}
|
|
|
|
func (s *Sprite) Frames() int { return len(s.frames) }
|
|
|
|
// Mirror returns s flipped left to right.
|
|
func (s *Sprite) Mirror() *Sprite {
|
|
m := &Sprite{W: s.W, H: s.H}
|
|
for _, px := range s.frames {
|
|
out := make([]int8, len(px))
|
|
for y := range s.H {
|
|
for x := range s.W {
|
|
out[y*s.W+x] = px[y*s.W+s.W-1-x]
|
|
}
|
|
}
|
|
m.frames = append(m.frames, out)
|
|
}
|
|
return m
|
|
}
|
|
|
|
// Sprite draws frame of sp; # takes ink; frames wrap.
|
|
func (g Canvas) Sprite(x, y int, sp *Sprite, frame int, ink Slot, scale int) {
|
|
g.sprite(x, y, sp, frame, ink, scale, false)
|
|
}
|
|
|
|
// Silhouette draws sp's shape all in one slot.
|
|
func (g Canvas) Silhouette(x, y int, sp *Sprite, frame int, s Slot, scale int) {
|
|
g.sprite(x, y, sp, frame, s, scale, true)
|
|
}
|
|
|
|
func (g Canvas) sprite(x, y int, sp *Sprite, frame int, ink Slot, scale int, mono bool) {
|
|
checkScale(scale)
|
|
n := len(sp.frames)
|
|
px := sp.frames[(frame%n+n)%n]
|
|
for row := range sp.H {
|
|
line := px[row*sp.W : (row+1)*sp.W]
|
|
for col := 0; col < sp.W; {
|
|
v := line[col]
|
|
end := col + 1
|
|
for end < sp.W && line[end] == v {
|
|
end++
|
|
}
|
|
if v != cellClear {
|
|
s := ink
|
|
if v >= 0 && !mono {
|
|
s = Slot(v)
|
|
}
|
|
g.Rect(x+col*scale, y+row*scale, (end-col)*scale, scale, s)
|
|
}
|
|
col = end
|
|
}
|
|
}
|
|
}
|