237 lines
6.0 KiB
Go
237 lines
6.0 KiB
Go
package clipboard
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
|
|
"aego/core/errs"
|
|
"aego/core/ids"
|
|
"aego/editor/core/document"
|
|
"aego/editor/core/history"
|
|
"aego/editor/core/history/commands"
|
|
"aego/scene"
|
|
"aego/scene/sceneio"
|
|
"aego/scene/scriptcomp"
|
|
"aego/schema"
|
|
)
|
|
|
|
type compData struct {
|
|
td *schema.TypeDescriptor
|
|
values []commands.FieldValue
|
|
}
|
|
|
|
func (e Env) pasteInto(tx *history.Transaction, d *document.SceneDocument, nodes []ids.NodeID, td *schema.TypeDescriptor, values []commands.FieldValue) error {
|
|
s := d.Scene()
|
|
env := commands.WithOnAdd(commands.EnvFor(s), e.OnAdd)
|
|
for _, n := range nodes {
|
|
h := s.Handle(n)
|
|
if h.IsZero() || !s.IsAlive(h) {
|
|
continue
|
|
}
|
|
if _, has := s.GetByType(h, td.ID); !has {
|
|
if !s.Accepts(h, td.ID) {
|
|
continue
|
|
}
|
|
if err := tx.Exec(commands.NewMutation("Add "+td.FullName, env, commands.AddComp(s, n, td, values))); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
for _, fv := range values {
|
|
fd, ok := td.FieldByID(fv.Field)
|
|
if !ok || fd.Flags.Has(schema.FlagReadOnly) {
|
|
continue
|
|
}
|
|
if err := d.Props.Set(tx, []ids.NodeID{n}, td.ID, fv.Field, fv.Value); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e Env) EncodeComponent(s *scene.Scene, node ids.NodeID, t schema.TypeID) ([]byte, error) {
|
|
td, ok := s.Registry().ByID(t)
|
|
if !ok {
|
|
return nil, errs.New(errs.SchemaUnknownType, "component type is not registered")
|
|
}
|
|
values, err := componentValues(s, node, td)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return e.encodeComponent(td, values)
|
|
}
|
|
|
|
func (e Env) ApplyComponentData(d *document.SceneDocument, nodes []ids.NodeID, data []byte, label string) error {
|
|
td, values, err := e.decodeComponent(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tx := d.History.Begin(label)
|
|
if err := e.pasteInto(tx, d, nodes, td, values); err != nil {
|
|
tx.Abort()
|
|
return err
|
|
}
|
|
tx.Commit()
|
|
return nil
|
|
}
|
|
|
|
func (e Env) CopyComponents(s *scene.Scene, node ids.NodeID, types []schema.TypeID) error {
|
|
if e.Clip == nil {
|
|
return errs.New(errs.EditorCommand, "clipboard is unavailable")
|
|
}
|
|
if len(types) == 0 {
|
|
return errs.New(errs.EditorCommand, "the node has no components to copy")
|
|
}
|
|
sc := e.scratch()
|
|
h := sc.Create(sc.Root(), "components")
|
|
sc.Flush()
|
|
for _, t := range types {
|
|
td, ok := s.Registry().ByID(t)
|
|
if !ok {
|
|
continue
|
|
}
|
|
values, err := componentValues(s, node, td)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := putComponent(sc, h, td, values); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
data := sceneio.SaveSubtree(sc, h, true)
|
|
e.Clip.SetData(MIMEComponents, data, componentsText+string(data))
|
|
return nil
|
|
}
|
|
|
|
func (e Env) readComponents() ([]compData, bool) {
|
|
c := e.Clip
|
|
if c == nil {
|
|
return nil, false
|
|
}
|
|
data, ok := c.Data(MIMEComponents)
|
|
if !ok || len(data) == 0 {
|
|
t := c.Text()
|
|
if !strings.HasPrefix(t, componentsText) {
|
|
return nil, false
|
|
}
|
|
data = []byte(strings.TrimPrefix(t, componentsText))
|
|
}
|
|
sc := e.scratch()
|
|
root, err := loadScratch(sc, data)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
reg := sc.Registry()
|
|
unknown, hasUnknown := reg.ByName("engine.Unknown")
|
|
var out []compData
|
|
var fail bool
|
|
sc.ComponentsOf(sc.Handle(root), func(t schema.TypeID) {
|
|
if fail || (hasUnknown && t == unknown.ID) || t == scriptcomp.ScriptTypeID {
|
|
return
|
|
}
|
|
td, ok := reg.ByID(t)
|
|
if !ok || td.Hidden {
|
|
return
|
|
}
|
|
values, err := componentValues(sc, root, td)
|
|
if err != nil {
|
|
fail = true
|
|
return
|
|
}
|
|
out = append(out, compData{td: td, values: values})
|
|
})
|
|
return out, !fail && len(out) > 0
|
|
}
|
|
|
|
func (e Env) PasteComponents(d *document.SceneDocument, nodes []ids.NodeID) error {
|
|
list, ok := e.readComponents()
|
|
if !ok {
|
|
return errs.New(errs.EditorCommand, "clipboard does not hold components")
|
|
}
|
|
if len(nodes) == 0 {
|
|
return errs.New(errs.EditorCommand, "select the nodes to paste the components onto")
|
|
}
|
|
tx := d.History.Begin("Paste Components")
|
|
for _, c := range list {
|
|
if err := e.pasteInto(tx, d, nodes, c.td, c.values); err != nil {
|
|
tx.Abort()
|
|
return err
|
|
}
|
|
}
|
|
tx.Commit()
|
|
return nil
|
|
}
|
|
|
|
func (e Env) EncodeScript(s *scene.Scene, node ids.NodeID, name string) ([]byte, error) {
|
|
sc, ok := scriptcomp.ScriptsOf(s, s.Handle(node))
|
|
i := -1
|
|
if ok {
|
|
i = sc.Index(name)
|
|
}
|
|
if i < 0 {
|
|
return nil, errs.New(errs.EditorCommand, "the node has no script "+name)
|
|
}
|
|
scr := e.scratch()
|
|
h := scr.Create(scr.Root(), "preset")
|
|
scr.Flush()
|
|
bag := schema.PropertyBag{Props: slices.Clone(sc.Attachments[i].Props.Props)}
|
|
add := &commands.AddScript{Node: scr.ID(h), TypeName: name, Props: bag}
|
|
if err := add.Do(target{scr}); err != nil {
|
|
return nil, err
|
|
}
|
|
return sceneio.SaveSubtree(scr, h, true), nil
|
|
}
|
|
|
|
func (e Env) decodeScript(data []byte) (string, schema.PropertyBag, error) {
|
|
sc := e.scratch()
|
|
root, err := loadScratch(sc, data)
|
|
if err != nil {
|
|
return "", schema.PropertyBag{}, err
|
|
}
|
|
scs, ok := scriptcomp.ScriptsOf(sc, sc.Handle(root))
|
|
if !ok || len(scs.Attachments) == 0 {
|
|
return "", schema.PropertyBag{}, errs.New(errs.EditorCommand, "the preset holds no script")
|
|
}
|
|
a := scs.Attachments[0]
|
|
return a.TypeName, a.Props, nil
|
|
}
|
|
|
|
func (e Env) ApplyScriptData(d *document.SceneDocument, nodes []ids.NodeID, data []byte, label string) error {
|
|
name, bag, err := e.decodeScript(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s := d.Scene()
|
|
td, ok := s.Registry().ByName(name)
|
|
if !ok || td.Kind != schema.TypeScript {
|
|
return errs.New(errs.EditorCommand, "script "+name+" is not compiled, the preset cannot be applied")
|
|
}
|
|
tx := d.History.Begin(label)
|
|
for _, n := range nodes {
|
|
h := s.Handle(n)
|
|
if h.IsZero() || !s.IsAlive(h) {
|
|
continue
|
|
}
|
|
if scs, ok := scriptcomp.ScriptsOf(s, h); !ok || scs.Index(name) < 0 {
|
|
if err := tx.Exec(&commands.AddScript{Node: n, TypeName: name, Props: schema.PropertyBag{Props: slices.Clone(bag.Props)}}); err != nil {
|
|
tx.Abort()
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
for _, pr := range bag.Props {
|
|
fd, ok := td.Field(pr.Name)
|
|
if !ok || fd.Flags.Has(schema.FlagReadOnly) {
|
|
continue
|
|
}
|
|
if err := d.Props.Set(tx, []ids.NodeID{n}, td.ID, fd.ID, pr.Value); err != nil {
|
|
tx.Abort()
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
tx.Commit()
|
|
return nil
|
|
}
|