Files

461 lines
9.0 KiB
Go

package context
import (
"fmt"
"strings"
"time"
"aego/asset"
"aego/asset/loader"
"aego/asset/pipeline"
"aego/core/ids"
"aego/core/log"
"aego/data"
"aego/editor/core/command"
"aego/editor/core/document"
"aego/editor/core/history"
"aego/editor/core/props"
"aego/editor/core/selection"
"aego/editor/core/shape"
"aego/engine"
"aego/engine/module"
"aego/engine/problems"
"aego/project"
"aego/render"
"aego/render/texture"
"aego/scene"
"aego/schema"
"aego/ui/core"
"aego/ui/dnd"
)
// Scene view tools stored in Settings.Tool.
const (
ToolSelect uint8 = iota
ToolMove
ToolRotate
ToolScale
)
const (
KindFiles = "files"
KindAsset = "asset"
KindAssets = "assets"
KindNodes = "nodes"
)
type Level uint8
const (
LevelInfo Level = iota
LevelWarn
LevelError
)
type Notice struct {
Level Level
Text string
Time float64
}
type DataClipboard interface {
core.Clipboard
SetData(mime string, data []byte, text string) bool
Data(mime string) ([]byte, bool)
}
type SceneSettings struct {
Tool uint8
Grid bool
Snap bool
SnapMove float32
SnapRotate float32
SnapScale float32
}
func DefaultSettings() SceneSettings {
return SceneSettings{Grid: true, SnapMove: 0.5, SnapRotate: 15, SnapScale: 0.1}
}
type PlayBridge interface {
CanCopyToEdit(nodes []ids.NodeID, t schema.TypeID) bool
CopyToEdit(nodes []ids.NodeID, t schema.TypeID) error
World() *engine.World
}
type ThumbSource interface {
ByID(id ids.AssetID) (texture.Region, bool)
}
const (
ProblemScript = "script"
ProblemScene = "scene"
)
type ScriptProblem struct {
Category string
Package string
Path string
Line int
Col int
Node ids.NodeID
Code string
Error bool
Block bool
Message string
Help string
}
func (p ScriptProblem) Script() bool {
return p.Category == "" || p.Category == ProblemScript
}
type ProblemSource func(dst []ScriptProblem) ([]ScriptProblem, uint64)
type ScriptTypeStats struct {
Name string
Instances int
Calls uint64
Time time.Duration
Faults int
}
type ScriptCoroutineStats struct {
Active int
Sleeping int
ResumesPerFrame uint64
Canceled uint64
}
type ScriptSignalStats struct {
Connections int
EmitsPerFrame uint64
HandlersPerFrame uint64
}
type ScriptStats struct {
Instances int
Update int
Fixed int
Instructions uint64
HostCalls uint64
HeapObjects int
HeapBytes int
GCRuns uint64
GCTime time.Duration
Faults int
BudgetAborts uint64
Coroutines ScriptCoroutineStats
Signals ScriptSignalStats
Types []ScriptTypeStats
}
type ScriptHost interface {
Errors() int
Attach(w *engine.World, s *scene.Scene) (func(), error)
Problems() ([]ScriptProblem, uint64)
Stats() (ScriptStats, bool)
Status() (generation uint64, latestFailed bool)
Watch(w *engine.World, node scene.NodeHandle, script, field string, lane int) (text string, num float64, isNum bool, ok bool)
}
type Editor struct {
BeforeSwap func(*pipeline.Batch) func()
Engine *engine.Engine
Renderer *render.Renderer
Project *project.Project
Pipeline *pipeline.Pipeline
Loader *loader.Loader
Types *scene.Types
Docs *document.Manager
Selection *selection.Service
Commands *command.Registry
UI *core.Context
DnD *dnd.State
Clipboard DataClipboard
Log *log.ChannelLogger
Settings SceneSettings
Thumbs ThumbSource
Scripts ScriptHost
Code ScriptNav
Shapes *shape.Registry
Problems *problems.Service
Sections *module.Sections
ShowProblems func(filter string)
RevealPath func(rel string)
OpenScript func(rel string, line int)
ShowDocs func(ref, query string)
ShowEvents func(frame uint64)
Crash func(where string, rec any, stack []byte)
NewScene func(rel string) error
NewPrefab func(rel string) error
FocusedPanel string
AssetRev uint64
RenderStats func() render.Stats
Play PlayBridge
Collapsed map[string]bool
Pages []SettingsPage
Vectors []VectorLayer
Profiler []ProfilerSection
Overlay Overlays
ShowSettings func(page string)
hooks []func()
questions []Question
notices []Notice
scratch []ids.NodeID
}
func (e *Editor) Now() float64 {
if e.Engine == nil {
return 0
}
return e.Engine.Platform().Now()
}
func (e *Editor) Doc() *document.SceneDocument {
return e.Docs.Active()
}
func (e *Editor) Scene() *scene.Scene {
if d := e.Doc(); d != nil {
return d.Scene()
}
return nil
}
func (e *Editor) History() *history.History {
if d := e.Doc(); d != nil {
return d.History
}
return nil
}
func (e *Editor) Props() *props.Access {
if d := e.Doc(); d != nil {
return d.Props
}
return nil
}
func (e *Editor) Selected() []ids.NodeID {
d := e.Doc()
if d == nil {
e.scratch = e.scratch[:0]
return e.scratch
}
e.scratch = e.Selection.Nodes(d.ID, e.scratch)
return e.scratch
}
func (e *Editor) items(nodes []ids.NodeID) []selection.Item {
d := e.Doc()
if d == nil {
return nil
}
out := make([]selection.Item, len(nodes))
for i, n := range nodes {
out[i] = selection.Item{Doc: d.ID, Node: n}
}
return out
}
func (e *Editor) SelectionState() *selection.Service {
return e.Selection
}
func (e *Editor) Documents() *document.Manager {
return e.Docs
}
func (e *Editor) Select(nodes ...ids.NodeID) {
e.Selection.Set(e.items(nodes)...)
}
func (e *Editor) AddToSelection(nodes ...ids.NodeID) {
e.Selection.Add(e.items(nodes)...)
}
func (e *Editor) ToggleSelection(n ids.NodeID) {
if d := e.Doc(); d != nil {
e.Selection.Toggle(selection.Item{Doc: d.ID, Node: n})
}
}
func (e *Editor) IsSelected(n ids.NodeID) bool {
d := e.Doc()
return d != nil && e.Selection.Contains(selection.Item{Doc: d.ID, Node: n})
}
func (e *Editor) PruneSelectionOf(doc document.ID) {
d := e.Docs.Get(doc)
if d == nil {
e.Selection.DropDoc(doc)
return
}
s := d.Scene()
e.Selection.Prune(doc, func(id ids.NodeID) bool {
h := s.Handle(id)
return !h.IsZero() && s.IsAlive(h)
})
}
func (e *Editor) Undo() error {
h := e.History()
if h == nil {
return nil
}
err := h.Undo()
e.PruneSelectionOf(e.Doc().ID)
return err
}
func (e *Editor) Redo() error {
h := e.History()
if h == nil {
return nil
}
err := h.Redo()
e.PruneSelectionOf(e.Doc().ID)
return err
}
func (e *Editor) Notify(l Level, text string) {
if e.Log != nil {
switch l {
case LevelError:
e.Log.Error(text)
case LevelWarn:
e.Log.Warn(text)
default:
e.Log.Info(text)
}
}
e.notices = append(e.notices, Notice{Level: l, Text: text, Time: e.Now()})
if len(e.notices) > 32 {
e.notices = append(e.notices[:0], e.notices[len(e.notices)-32:]...)
}
}
func (e *Editor) LastNotice() (Notice, bool) {
if n := len(e.notices); n > 0 {
return e.notices[n-1], true
}
return Notice{}, false
}
func (e *Editor) Notices() []Notice {
return e.notices
}
type AssetPayload struct {
ID ids.AssetID
Kind asset.ArtifactKind
Name string
}
type AssetsPayload struct {
Items []AssetPayload
Dirs []string
}
type NodesPayload struct {
Doc document.ID
Nodes []ids.NodeID
}
func AssetMatches(fieldKind string, kind asset.ArtifactKind) bool {
switch {
case fieldKind == "scene":
return kind == asset.ArtScene
case fieldKind == "prefab":
return kind == asset.ArtPrefab
case fieldKind == "mesh":
return kind == asset.ArtMesh
case fieldKind == data.KindData || strings.HasPrefix(fieldKind, data.KindData+":"):
return kind == asset.ArtData
case fieldKind == data.KindTable || strings.HasPrefix(fieldKind, data.KindTable+":"):
return kind == asset.ArtTable
}
return kind == asset.ArtTexture || kind == asset.ArtSpriteRegion
}
func (e *Editor) CommandItem(id, label string) bool {
return command.MenuItem(e.UI, e.Commands, id, label)
}
type Question struct {
Title string
Message string
Buttons []string
Fields []string
Values []string
Then func(choice int)
Submit func(values []string)
}
func (e *Editor) Ask(q Question) {
e.questions = append(e.questions, q)
}
func (e *Editor) TakeQuestions() []Question {
q := e.questions
e.questions = nil
return q
}
func (e *Editor) OnRender(fn func()) {
e.hooks = append(e.hooks, fn)
}
func (e *Editor) RunRenderHooks() {
for i, fn := range e.hooks {
func() {
defer func() {
if rec := recover(); rec != nil {
e.Notify(LevelError, fmt.Sprint("render hook crashed: ", rec))
}
}()
fn()
}()
e.hooks[i] = nil
}
e.hooks = e.hooks[:0]
}
func (e *Editor) ApplyBatch(b *pipeline.Batch) {
if b == nil {
return
}
var after func()
if e.BeforeSwap != nil {
after = e.BeforeSwap(b)
}
if e.Loader != nil && e.Pipeline != nil {
if len(b.Imported) > 0 {
if err := e.Loader.Swap(b.Imported, e.Pipeline.Artifacts()); err != nil {
e.Notify(LevelError, "hot reload failed, keeping the previous assets: "+err.Error())
}
}
for _, id := range b.Removed {
e.Loader.Drop(id)
}
}
if after != nil {
after()
}
for id, err := range b.Failed {
e.Notify(LevelWarn, "import failed for "+id.String()[:8]+": "+err.Error())
}
e.AssetRev++
}
func (e *Editor) PlayWorld() *engine.World {
if e.Play == nil {
return nil
}
return e.Play.World()
}