234 lines
5.6 KiB
Go
234 lines
5.6 KiB
Go
package health
|
|
|
|
import (
|
|
"strconv"
|
|
"time"
|
|
|
|
"aego/asset/pipeline"
|
|
"aego/core/mathx"
|
|
core "aego/editor/core/health"
|
|
"aego/editor/core/services"
|
|
"aego/editor/core/tasks"
|
|
"aego/engine/problems"
|
|
"aego/project"
|
|
uicore "aego/ui/core"
|
|
"aego/ui/widgets"
|
|
)
|
|
|
|
const ID = "project_health"
|
|
|
|
type Deps struct {
|
|
Pipeline *pipeline.Pipeline
|
|
Project *project.Project
|
|
Docs services.Documents
|
|
Tasks *tasks.Queue
|
|
Problems func() []problems.Diagnostic
|
|
Missing func() int
|
|
Cameras func() []string
|
|
Exclude func() []string
|
|
TextureLimit int
|
|
ShowMissing func()
|
|
ShowProblems func()
|
|
Reveal func(rel string)
|
|
Export func() func() []core.Item
|
|
}
|
|
|
|
type Panel struct {
|
|
d Deps
|
|
items []core.Item
|
|
extra []core.Item
|
|
at time.Time
|
|
open map[string]bool
|
|
running *tasks.Task
|
|
scroll float32
|
|
}
|
|
|
|
func New(d Deps) *Panel {
|
|
return &Panel{d: d, open: map[string]bool{}}
|
|
}
|
|
|
|
func (p *Panel) ID() string {
|
|
return ID
|
|
}
|
|
|
|
func (p *Panel) Title() string {
|
|
return "Project Health"
|
|
}
|
|
|
|
func (p *Panel) Icon() string {
|
|
return "panel.health"
|
|
}
|
|
|
|
func (p *Panel) Run() {
|
|
env := core.Env{Pipeline: p.d.Pipeline, Project: p.d.Project}
|
|
if p.d.Problems != nil {
|
|
env.Problems = p.d.Problems()
|
|
}
|
|
if p.d.Missing != nil {
|
|
env.Missing = p.d.Missing()
|
|
}
|
|
if p.d.Exclude != nil {
|
|
env.Exclude = p.d.Exclude()
|
|
}
|
|
p.items, p.at = core.Run(env), time.Now()
|
|
if p.running != nil || p.d.Tasks == nil {
|
|
return
|
|
}
|
|
var cams []string
|
|
if p.d.Cameras != nil {
|
|
cams = p.d.Cameras()
|
|
}
|
|
var exp func() []core.Item
|
|
if p.d.Export != nil {
|
|
exp = p.d.Export()
|
|
}
|
|
var found []core.Item
|
|
pl, limit := p.d.Pipeline, p.d.TextureLimit
|
|
p.running = p.d.Tasks.Work(tasks.Spec{Title: "Check textures, scenes and export", Kind: "health"}, func(*tasks.Task) error {
|
|
found = core.Scan(pl, cams, limit)
|
|
if exp != nil {
|
|
found = append(found, exp()...)
|
|
}
|
|
return nil
|
|
}).Then(func(t *tasks.Task) {
|
|
p.running = nil
|
|
if t.State() == tasks.Done {
|
|
p.extra = found
|
|
}
|
|
})
|
|
}
|
|
|
|
func (p *Panel) all() []core.Item {
|
|
return append(append([]core.Item(nil), p.items...), p.extra...)
|
|
}
|
|
|
|
func (p *Panel) Draw(f *services.Frame, r mathx.Rect) {
|
|
ui := f.UI
|
|
th := &ui.Theme
|
|
if p.at.IsZero() {
|
|
p.Run()
|
|
}
|
|
head := ui.Cursor().Next(0)
|
|
status := "Checked at " + p.at.Format("15:04:05")
|
|
if p.running != nil {
|
|
status += ", scanning textures…"
|
|
}
|
|
ui.Text(status, mathx.Vec2{X: head.X, Y: ui.TextY(head)}, th.TextDim)
|
|
bw := ui.TextSize("Run Checks").X + 16
|
|
if widgets.ButtonRect(ui, ui.ID("run"), mathx.Rect{X: head.X + head.W - bw, Y: head.Y, W: bw, H: head.H}, "Run Checks") {
|
|
p.Run()
|
|
}
|
|
p.loadProfile(ui)
|
|
items := p.all()
|
|
if len(items) == 0 {
|
|
widgets.LabelDim(ui, "No problems found")
|
|
return
|
|
}
|
|
type line struct {
|
|
item int
|
|
path int
|
|
}
|
|
var lines []line
|
|
for i, it := range items {
|
|
lines = append(lines, line{item: i, path: -1})
|
|
if p.open[it.Code+it.Title] {
|
|
for j := range it.Paths[:min(len(it.Paths), 200)] {
|
|
lines = append(lines, line{item: i, path: j})
|
|
}
|
|
}
|
|
}
|
|
top := ui.Cursor().Y
|
|
list := mathx.Rect{X: r.X, Y: top, W: r.W, H: max(0, r.Y+r.H-top)}
|
|
widgets.VirtualList(ui, ui.ID("rows"), list, len(lines), th.RowHeight+2, &p.scroll, func(i int, rr mathx.Rect) {
|
|
ln := lines[i]
|
|
it := items[ln.item]
|
|
if ln.path >= 0 {
|
|
rel := core.PathOf(it.Paths[ln.path])
|
|
id := uicore.IDSeq(ui.ID("path"), i)
|
|
if ui.Clicked(id, rr) && p.d.Reveal != nil {
|
|
p.d.Reveal(rel)
|
|
}
|
|
if ui.Hot() == id {
|
|
ui.Rect(rr, th.WidgetHot)
|
|
}
|
|
ui.PushClip(rr)
|
|
ui.Text(it.Paths[ln.path], mathx.Vec2{X: rr.X + 24 + th.Indent, Y: ui.TextY(rr)}, th.TextDim)
|
|
ui.PopClip()
|
|
return
|
|
}
|
|
p.itemRow(ui, it, i, rr)
|
|
})
|
|
}
|
|
|
|
func (p *Panel) itemRow(ui *uicore.Context, it core.Item, i int, rr mathx.Rect) {
|
|
th := &ui.Theme
|
|
key := it.Code + it.Title
|
|
icon, col := "log.info", th.TextDim
|
|
switch it.Severity {
|
|
case core.Error:
|
|
icon, col = "log.error", th.Error
|
|
case core.Warning:
|
|
icon, col = "status.warning", th.Warn
|
|
}
|
|
id := uicore.IDSeq(ui.ID("item"), i)
|
|
btn := ""
|
|
switch it.Action {
|
|
case core.ActionMissing:
|
|
btn = "Resolve…"
|
|
case core.ActionProblems:
|
|
btn = "Show"
|
|
}
|
|
bw := float32(0)
|
|
if btn != "" {
|
|
bw = ui.TextSize(btn).X + 16
|
|
if widgets.ButtonRect(ui, uicore.IDSeq(id, 1), mathx.Rect{X: rr.X + rr.W - bw, Y: rr.Y + 1, W: bw, H: rr.H - 2}, btn) {
|
|
switch it.Action {
|
|
case core.ActionMissing:
|
|
if p.d.ShowMissing != nil {
|
|
p.d.ShowMissing()
|
|
}
|
|
case core.ActionProblems:
|
|
if p.d.ShowProblems != nil {
|
|
p.d.ShowProblems()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
body := mathx.Rect{X: rr.X, Y: rr.Y, W: rr.W - bw - 4, H: rr.H}
|
|
if len(it.Paths) > 0 && ui.Clicked(id, body) {
|
|
p.open[key] = !p.open[key]
|
|
}
|
|
if len(it.Paths) > 0 {
|
|
widgets.Chevron(ui, mathx.Rect{X: rr.X, Y: rr.Y, W: th.Indent, H: rr.H}, p.open[key], th.TextDim)
|
|
}
|
|
widgets.IconAt(ui, mathx.Rect{X: rr.X + th.Indent, Y: rr.Y + (rr.H-16)/2, W: 16, H: 16}, icon, col)
|
|
ui.PushClip(body)
|
|
ui.Text(it.Title, mathx.Vec2{X: rr.X + th.Indent + 20, Y: ui.TextY(rr)}, th.Text)
|
|
ui.TextIn(body, it.Code, th.TextDisabled, uicore.AlignRight)
|
|
ui.PopClip()
|
|
}
|
|
|
|
func (p *Panel) loadProfile(ui *uicore.Context) {
|
|
d := p.d.Docs.Doc()
|
|
if d == nil || d.Load.Took == 0 {
|
|
return
|
|
}
|
|
deps, missing := 0, 0
|
|
if id, ok := p.d.Pipeline.Lookup(d.Path); ok {
|
|
if e, ok := p.d.Pipeline.Entry(id); ok {
|
|
deps = len(e.Deps)
|
|
for _, dep := range e.Deps {
|
|
if _, ok := p.d.Pipeline.PathOf(dep); !ok {
|
|
missing++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
text := d.Path + ": opened in " + strconv.FormatInt(d.Load.Took.Milliseconds(), 10) + " ms, " +
|
|
strconv.Itoa(d.Load.Nodes) + " node(s), " + strconv.Itoa(deps) + " asset(s)"
|
|
if missing > 0 {
|
|
text += ", " + strconv.Itoa(missing) + " missing"
|
|
}
|
|
widgets.LabelDim(ui, text)
|
|
}
|