Files

238 lines
5.0 KiB
Go

package welcome
import (
"io/fs"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"aego/core/mathx"
"aego/editor/core/document"
"aego/editor/core/services"
"aego/ui/widgets"
)
const (
ID = "welcome"
stateFile = ".aego/user/getting-started"
ExamplePath = "scripts/hello/spinner.aes"
)
const example = `package hello
import (
"aego/input"
"aego/math"
)
// Spinner turns its node and moves it with A/D or the arrow keys.
// Attach it to a node, press Play, then change Speed in the Inspector.
script Spinner {
// Turn speed in degrees per second.
@export(min=0, max=720, unit="deg/s")
var Speed float = 90
// Move speed in world units per second.
@export(min=0, max=20, unit="u/s")
var Move float = 4
func Update(dt float) {
node.SetRotation(node.Rotation() + math.Deg2Rad(Speed)*dt)
p := node.Position()
p.X += input.Axis("horizontal") * Move * dt
node.SetPosition(p)
}
}
`
type Check struct {
ID string
Title string
Hint string
Done func() bool
Show func()
}
type Panel struct {
Reference func()
NewProject func()
Empty func() bool
Heading string
Outro string
root string
checks []Check
done map[string]bool
scroll float32
content float32
}
func statePath(root string) string {
return filepath.Join(root, filepath.FromSlash(stateFile))
}
func FirstRun(root string) bool {
_, err := os.Stat(statePath(root))
return os.IsNotExist(err)
}
func Example(scripts string) (bool, error) {
found := false
filepath.WalkDir(scripts, func(p string, d fs.DirEntry, err error) error {
if err == nil && !d.IsDir() && filepath.Ext(p) == ".aes" {
found = true
return filepath.SkipAll
}
return nil
})
if found {
return false, nil
}
return true, document.WriteAtomic(filepath.Join(scripts, "hello", "spinner.aes"), []byte(example))
}
func New(root string, checks []Check) *Panel {
p := &Panel{root: root, checks: checks, done: map[string]bool{}}
if data, err := os.ReadFile(statePath(root)); err == nil {
for _, l := range strings.Split(string(data), "\n") {
if l = strings.TrimSpace(l); l != "" {
p.done[l] = true
}
}
}
return p
}
func (p *Panel) ID() string {
return ID
}
func (p *Panel) Title() string {
return "Getting Started"
}
func (p *Panel) Save() error {
keys := make([]string, 0, len(p.done))
for k := range p.done {
keys = append(keys, k)
}
sort.Strings(keys)
return document.WriteAtomic(statePath(p.root), []byte(strings.Join(keys, "\n")+"\n"))
}
func (p *Panel) Done(id string) bool {
return p.done[id]
}
func (p *Panel) Mark(id string) {
if p.done[id] {
return
}
p.done[id] = true
p.Save()
}
func (p *Panel) count() int {
n := 0
for _, c := range p.checks {
if p.done[c.ID] {
n++
}
}
return n
}
func (p *Panel) Complete() bool {
return p.count() == len(p.checks)
}
func (p *Panel) Tick() {
changed := false
for _, c := range p.checks {
if !p.done[c.ID] && c.Done != nil && c.Done() {
p.done[c.ID] = true
changed = true
}
}
if changed {
p.Save()
}
}
func (p *Panel) Draw(f *services.Frame, r mathx.Rect) {
ui := f.UI
th := &ui.Theme
if ui.MouseOver(ui.ID("scroll"), r) {
if w := ui.ScrollY(th.RowHeight * 3); w != 0 {
p.scroll -= w
}
}
p.scroll = mathx.Clamp(p.scroll, 0, max(0, p.content-r.H+th.Padding*2))
cur := ui.Cursor()
top := cur.Y
cur.Y -= p.scroll
heading := p.Heading
if heading == "" {
heading = "Getting started with Aego"
}
widgets.Label(ui, heading)
if p.NewProject != nil && (p.Empty == nil || p.Empty()) {
if widgets.Button(ui, "Create a project…") {
p.NewProject()
}
}
n := p.count()
widgets.LabelDim(ui, strconv.Itoa(n)+" of "+strconv.Itoa(len(p.checks))+" done")
bar := cur.Next(6)
ui.Rect(bar, th.Widget)
if len(p.checks) > 0 {
ui.Rect(mathx.Rect{X: bar.X, Y: bar.Y, W: bar.W * float32(n) / float32(len(p.checks)), H: bar.H}, th.Accent)
}
cur.Y += 6
for i, c := range p.checks {
ui.PushID(c.ID)
row := cur.Next(th.RowHeight + 4)
box := mathx.Rect{X: row.X, Y: row.Y + 4, W: row.H - 8, H: row.H - 8}
done := p.done[c.ID]
col := th.Text
if done {
ui.Rect(box, th.Ok)
widgets.CheckMark(ui, box, th.Background)
col = th.TextDim
} else {
ui.Rect(box, th.Widget)
ui.Outline(box, th.Border)
}
bw := float32(84)
ui.PushClip(mathx.Rect{X: row.X, Y: row.Y, W: max(0, row.W-bw-6), H: row.H})
ui.Text(strconv.Itoa(i+1)+". "+c.Title, mathx.Vec2{X: box.X + box.W + 8, Y: ui.TextY(row)}, col)
ui.PopClip()
if c.Show != nil && !done {
if widgets.ButtonRect(ui, ui.ID("show"), mathx.Rect{X: row.X + row.W - bw, Y: row.Y + 2, W: bw, H: row.H - 4}, "Show me") {
c.Show()
}
}
if c.Hint != "" {
cur.X += box.W + 8
cur.W -= box.W + 8
widgets.LabelWrapped(ui, c.Hint, true)
cur.X -= box.W + 8
cur.W += box.W + 8
}
cur.Y += 4
ui.PopID()
}
if p.Complete() && p.Outro != "" {
widgets.LabelWrapped(ui, p.Outro, false)
}
if p.Reference != nil && widgets.Button(ui, "Open Documentation") {
p.Reference()
}
if widgets.Button(ui, "Reset checklist") {
clear(p.done)
p.Save()
}
p.content = cur.Y + p.scroll - top
}