Files

190 lines
4.9 KiB
Go

package references
import (
"aego/asset"
"path"
"strconv"
"time"
"aego/core/ids"
"aego/core/mathx"
"aego/editor/core/context"
"aego/editor/core/refs"
"aego/editor/core/services"
"aego/ui/core"
"aego/ui/widgets"
)
type Missing struct {
d Deps
assets []refs.Missing
nodes []refs.MissingNode
rev uint64
at time.Time
scroll float32
pick string
picking int
}
func NewMissing(d Deps) *Missing {
return &Missing{d: d, picking: -1}
}
func (m *Missing) ID() string {
return MissingID
}
func (m *Missing) Title() string {
return "Missing References"
}
func (m *Missing) Icon() string {
return "status.warning"
}
func (m *Missing) Refresh() {
m.assets = m.d.Refs.MissingAssets()
m.nodes = m.d.Refs.MissingNodes()
m.rev = m.d.Pipeline.Revision()
m.at = time.Now()
}
func (m *Missing) Count() int {
return len(m.assets) + len(m.nodes)
}
func (m *Missing) Draw(f *services.Frame, r mathx.Rect) {
ui := f.UI
th := &ui.Theme
if m.at.IsZero() || (m.rev != m.d.Pipeline.Revision() && time.Since(m.at) > time.Second) {
m.Refresh()
}
head := ui.Cursor().Next(0)
rw := ui.TextSize("Rescan").X + 16
ui.Text(strconv.Itoa(len(m.assets))+" missing asset(s), "+strconv.Itoa(len(m.nodes))+" missing node reference(s)", mathx.Vec2{X: head.X, Y: ui.TextY(head)}, th.TextDim)
if widgets.ButtonRect(ui, ui.ID("rescan"), mathx.Rect{X: head.X + head.W - rw, Y: head.Y, W: rw, H: head.H}, "Rescan") {
m.Refresh()
}
if m.Count() == 0 {
widgets.LabelDim(ui, "Every reference resolves")
return
}
top := ui.Cursor().Y
list := mathx.Rect{X: r.X, Y: top, W: r.W, H: max(0, r.Y+r.H-top)}
rowH := th.RowHeight + 4
total := len(m.assets) + len(m.nodes)
widgets.VirtualList(ui, ui.ID("rows"), list, total, rowH, &m.scroll, func(i int, rr mathx.Rect) {
if i < len(m.assets) {
m.assetRow(ui, i, rr)
} else {
m.nodeRow(ui, i-len(m.assets), rr)
}
})
}
func (m *Missing) buttons(ui *core.Context, rr mathx.Rect, id core.ID, labels ...string) int {
x := rr.X + rr.W
hit := -1
for i := len(labels) - 1; i >= 0; i-- {
w := ui.TextSize(labels[i]).X + 14
x -= w + 4
if widgets.ButtonRect(ui, core.IDSeq(id, i+1), mathx.Rect{X: x, Y: rr.Y + 2, W: w, H: rr.H - 4}, labels[i]) {
hit = i
}
}
return hit
}
func (m *Missing) assetRow(ui *core.Context, i int, rr mathx.Rect) {
th := &ui.Theme
it := m.assets[i]
id := core.IDSeq(ui.ID("asset"), i)
widgets.IconAt(ui, mathx.Rect{X: rr.X + 2, Y: rr.Y + (rr.H-16)/2, W: 16, H: 16}, "status.warning", th.Warn)
text := it.Label() + " · " + strconv.Itoa(len(it.Refs)) + " referrer(s)"
labels := []string{"Show", "Replace…", "Clear"}
if !it.Suggest.IsZero() {
labels = append([]string{"Use " + path.Base(it.Path)}, labels...)
}
bw := float32(0)
for _, l := range labels {
bw += ui.TextSize(l).X + 18
}
ui.PushClip(mathx.Rect{X: rr.X, Y: rr.Y, W: max(0, rr.W-bw), H: rr.H})
ui.Text(text, mathx.Vec2{X: rr.X + 22, Y: ui.TextY(rr)}, th.Text)
ui.PopClip()
tip := "Referenced by:"
for k, ref := range it.Refs {
if k == 8 {
tip += "\n…"
break
}
tip += "\n" + ref.Path
}
ui.MouseOver(id, rr)
widgets.Tooltip(ui, id, tip)
hit := m.buttons(ui, rr, id, labels...)
if hit < 0 {
m.picker(ui, i, rr)
return
}
if !it.Suggest.IsZero() {
if hit == 0 {
runReplace(m.d, it.ID, it.Suggest, "Resolve "+path.Base(it.Label()), m.Refresh)
return
}
hit--
}
switch hit {
case 0:
if m.d.Show != nil {
m.d.Show(it.ID)
}
case 1:
m.picking, m.pick = i, ""
ui.CloseAllPopups()
ui.OpenPopup(core.IDSeq(ui.ID("pick"), i), mathx.Vec2{X: rr.X + rr.W/2, Y: rr.Y + rr.H})
case 2:
runReplace(m.d, it.ID, ids.AssetID{}, "Clear "+path.Base(it.Label()), m.Refresh)
}
m.picker(ui, i, rr)
}
func (m *Missing) picker(ui *core.Context, i int, rr mathx.Rect) {
if m.picking != i {
return
}
it := m.assets[i]
if to, ok := pickAsset(ui, core.IDSeq(ui.ID("pick"), i), m.d.Pipeline, it.Grave.Kind, &m.pick, ids.AssetID{}); ok {
m.picking = -1
runReplace(m.d, it.ID, to, "Resolve "+path.Base(it.Label()), m.Refresh)
}
}
func (m *Missing) nodeRow(ui *core.Context, i int, rr mathx.Rect) {
th := &ui.Theme
it := m.nodes[i]
id := core.IDSeq(ui.ID("node"), i)
widgets.IconAt(ui, mathx.Rect{X: rr.X + 2, Y: rr.Y + (rr.H-16)/2, W: 16, H: 16}, "cat.node", th.Warn)
text := it.Scene + " · " + it.Path + " · " + it.Owner + "." + it.Field + " → missing node"
labels := []string{"Select", "Clear"}
ui.PushClip(mathx.Rect{X: rr.X, Y: rr.Y, W: max(0, rr.W-130), H: rr.H})
ui.Text(text, mathx.Vec2{X: rr.X + 22, Y: ui.TextY(rr)}, th.Text)
ui.PopClip()
switch m.buttons(ui, rr, id, labels...) {
case 0:
openPlace(m.d, refs.Ref{Path: it.Scene, Kind: sceneKind(it.Scene)}, refs.Place{Node: it.Node})
case 1:
if err := m.d.Refs.ClearNode(it); err != nil {
m.d.Notices.Notify(context.LevelError, err.Error())
}
m.Refresh()
}
}
func sceneKind(rel string) asset.ArtifactKind {
if path.Ext(rel) == ".aeprefab" {
return asset.ArtPrefab
}
return asset.ArtScene
}