500 lines
19 KiB
Go
500 lines
19 KiB
Go
package editor
|
|
|
|
import (
|
|
"bytes"
|
|
stdctx "context"
|
|
"image"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"aego/art"
|
|
"aego/asset/importers/artsource"
|
|
"aego/asset/pipeline"
|
|
"aego/core/ids"
|
|
"aego/core/log"
|
|
"aego/core/mathx"
|
|
"aego/editor/core/artedit"
|
|
"aego/editor/core/command"
|
|
"aego/editor/core/context"
|
|
"aego/editor/core/document"
|
|
"aego/editor/core/services"
|
|
"aego/editor/core/workspace"
|
|
"aego/editor/ext"
|
|
"aego/editor/ext/inspect"
|
|
"aego/modules/render2d"
|
|
"aego/platform/input"
|
|
"aego/project"
|
|
"aego/scene"
|
|
"aego/scene/sceneio"
|
|
"aego/ui/core"
|
|
)
|
|
|
|
func TestArtColorPickerPreservesDisplayedBytes(t *testing.T) {
|
|
for _, want := range []art.Color{{R: 1, G: 50, B: 200, A: 128}, {R: 127, G: 128, B: 255, A: 255}, {R: 255, G: 0, B: 0, A: 0}} {
|
|
got := artColorBytes(mathx.ColorRGBA8(want.R, want.G, want.B, want.A))
|
|
if got != want {
|
|
t.Errorf("color picker bytes = %+v, want %+v", got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Exercises the native panel and real document/import pipeline with simulated
|
|
// pointer events. This is not a replacement for an OS/GPU desktop smoke test.
|
|
func TestArtCreateDrawHistorySaveAndReopen(t *testing.T) {
|
|
p, err := project.Create(t.TempDir(), "Art workflow", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
importers := pipeline.NewImporterRegistry()
|
|
if err := importers.Register(artsource.New()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
pl, err := pipeline.Open(p, importers, log.Nop())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(pl.Close)
|
|
commands := command.New()
|
|
ed := &context.Editor{Pipeline: pl, Docs: document.NewManager(document.Options{Root: p.Root}), Commands: commands, UI: core.New(nil)}
|
|
ws := workspace.New(p.Root, nil)
|
|
registry := ext.New(ext.Deps{Editor: ed, Workspace: ws, Commands: commands})
|
|
if err := registry.Run("render2d", imageHooks); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := commands.Run("asset.new_art"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
questions := ed.TakeQuestions()
|
|
if len(questions) != 1 || questions[0].Submit == nil {
|
|
t.Fatal("new art command did not open the creation dialog")
|
|
}
|
|
questions[0].Submit([]string{"assets/hero.aeart", "16", "16", "rgba"})
|
|
if notice, ok := ed.LastNotice(); ok && notice.Level == context.LevelError {
|
|
t.Fatalf("creation failed: %s", notice.Text)
|
|
}
|
|
id, ok := pl.Lookup("assets/hero.aeart")
|
|
if !ok {
|
|
t.Fatal("created art source is not in the asset index")
|
|
}
|
|
doc := ed.Docs.AssetDocument(id)
|
|
if doc == nil || doc.Handler != artHandlerID || doc.Metadata == nil {
|
|
t.Fatalf("source did not open in the shared art document with metadata: %+v", doc)
|
|
}
|
|
model, ok := doc.Binding.Model.(*artedit.Document)
|
|
if !ok || model.Session == nil {
|
|
t.Fatal("panel did not bind the canonical art document")
|
|
}
|
|
panelID := artHandlerID + "/" + id.String()
|
|
panel, ok := ws.Panel(panelID)
|
|
if !ok || !ws.Visible(panelID) {
|
|
t.Fatal("art document did not open in the workspace")
|
|
}
|
|
initial, err := os.ReadFile(ed.Docs.Abs(doc.Path))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state := &input.State{}
|
|
draw := func() {
|
|
ed.UI.Begin(state, mathx.Vec2{X: 900, Y: 700}, 1)
|
|
panel.Draw(&services.Frame{UI: ed.UI, Focused: true}, mathx.Rect{W: 900, H: 700})
|
|
ed.UI.End()
|
|
}
|
|
state.BeginFrame()
|
|
draw()
|
|
point := mathx.Vec2{X: 320, Y: 250}
|
|
// An unresolved recovery must not be replaced by a new pointer gesture.
|
|
model.PendingRecovery = true
|
|
state.BeginFrame()
|
|
draw()
|
|
state.BeginFrame()
|
|
state.ApplyMouseMove(point, mathx.Vec2{})
|
|
state.ApplyMouseDown(point, input.MouseLeft)
|
|
draw()
|
|
state.BeginFrame()
|
|
state.ApplyMouseUp(point, input.MouseLeft)
|
|
draw()
|
|
if doc.Dirty() || model.History.Cursor() != 0 || model.Session.Info().Gesture {
|
|
t.Fatal("canvas edited a drawing with an unresolved recovery")
|
|
}
|
|
model.PendingRecovery = false
|
|
state.BeginFrame()
|
|
draw()
|
|
state.BeginFrame()
|
|
state.ApplyMouseMove(point, mathx.Vec2{})
|
|
state.ApplyMouseDown(point, input.MouseLeft)
|
|
draw()
|
|
state.BeginFrame()
|
|
state.ApplyMouseUp(point, input.MouseLeft)
|
|
draw()
|
|
if !doc.Dirty() || model.History.Cursor() != 1 {
|
|
t.Fatalf("canvas stroke did not create one dirty history entry: dirty=%v cursor=%d", doc.Dirty(), model.History.Cursor())
|
|
}
|
|
frame := model.Session.Frame
|
|
painted, _, err := model.Capture(frame, art.Albedo)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if bytes.Count(painted.Pix, []byte{255}) == 0 {
|
|
t.Fatal("native pointer stroke did not paint the canonical pixels")
|
|
}
|
|
// Select Eyedropper and sample the just-painted pixel through the actual
|
|
// canvas widget. Picking a color is presentation state, not a document edit.
|
|
panelView := &artView{size: 1, grid: true, refresh: true, tool: artEyedropper, color: mathx.ColorRGBA8(255, 0, 0, 255)}
|
|
sampler := &artPanel{ctx: ed, reg: registry, doc: doc, model: model, view: panelView}
|
|
drawSampler := func() {
|
|
ed.UI.Begin(state, mathx.Vec2{X: 900, Y: 700}, 1)
|
|
sampler.Draw(&services.Frame{UI: ed.UI, Focused: true}, mathx.Rect{W: 900, H: 700})
|
|
ed.UI.End()
|
|
}
|
|
state.BeginFrame()
|
|
drawSampler()
|
|
state.BeginFrame()
|
|
state.ApplyMouseMove(point, mathx.Vec2{})
|
|
state.ApplyMouseDown(point, input.MouseLeft)
|
|
drawSampler()
|
|
state.BeginFrame()
|
|
state.ApplyMouseUp(point, input.MouseLeft)
|
|
drawSampler()
|
|
if panelView.color != mathx.White || model.History.Cursor() != 1 || !doc.Dirty() {
|
|
t.Fatalf("eyedropper did not pick the canvas pixel without an edit: color=%+v history=%d", panelView.color, model.History.Cursor())
|
|
}
|
|
// The Color Picker button uses a one-shot eyedropper and must restore the
|
|
// exact previous tool without adding another painting history entry.
|
|
panelView.chooseTool(1)
|
|
sampler.toggleOneShotPicker()
|
|
if panelView.tool != artEyedropper || !panelView.pickOnce {
|
|
t.Fatal("Color Picker did not start the temporary eyedropper")
|
|
}
|
|
state.BeginFrame()
|
|
state.ApplyMouseMove(point, mathx.Vec2{})
|
|
state.ApplyMouseDown(point, input.MouseLeft)
|
|
drawSampler()
|
|
state.BeginFrame()
|
|
state.ApplyMouseUp(point, input.MouseLeft)
|
|
drawSampler()
|
|
if panelView.tool != 1 || panelView.pickOnce || model.History.Cursor() != 1 {
|
|
t.Fatal("sampling did not return to the eraser without editing the document")
|
|
}
|
|
sampler.toggleOneShotPicker()
|
|
panelView.chooseTool(0)
|
|
if panelView.tool != 0 || panelView.pickOnce {
|
|
t.Fatal("explicit tool selection did not cancel the temporary eyedropper")
|
|
}
|
|
dragSelection := func(tool int, points ...mathx.Vec2) {
|
|
t.Helper()
|
|
panelView.tool = tool
|
|
state.BeginFrame()
|
|
state.ApplyMouseMove(points[0], mathx.Vec2{})
|
|
state.ApplyMouseDown(points[0], input.MouseLeft)
|
|
drawSampler()
|
|
for i := 1; i < len(points); i++ {
|
|
state.BeginFrame()
|
|
state.ApplyMouseMove(points[i], points[i].Sub(points[i-1]))
|
|
drawSampler()
|
|
}
|
|
state.BeginFrame()
|
|
state.ApplyMouseUp(points[len(points)-1], input.MouseLeft)
|
|
drawSampler()
|
|
}
|
|
dragSelection(artEllipseSelect, mathx.Vec2{X: 300, Y: 210}, mathx.Vec2{X: 455, Y: 355})
|
|
selection := model.Session.Selection
|
|
if selection == nil || selection.Count() == 0 || selection.Count() >= selection.Bounds().Dx()*selection.Bounds().Dy() || model.History.Cursor() != 1 {
|
|
t.Fatalf("ellipse selection was not a non-rectangular mask without history: %+v", selection)
|
|
}
|
|
if edges, simplified := artSelectionOutline(selection); simplified || len(edges) <= 4 {
|
|
t.Fatalf("ellipse outline did not follow the mask: %d edges simplified=%v", len(edges), simplified)
|
|
}
|
|
dragSelection(artLasso, mathx.Vec2{X: 300, Y: 210}, mathx.Vec2{X: 455, Y: 210}, mathx.Vec2{X: 455, Y: 355}, mathx.Vec2{X: 300, Y: 355})
|
|
if selection = model.Session.Selection; selection == nil || selection.Count() == 0 || model.History.Cursor() != 1 {
|
|
t.Fatal("lasso selection did not create a mask without editing the document")
|
|
}
|
|
// An external edit during the drag must not publish a selection against a
|
|
// different frame or revision, and losing focus must cancel the draft.
|
|
panelView.tool = artRectSelect
|
|
state.BeginFrame()
|
|
state.ApplyMouseMove(point, mathx.Vec2{})
|
|
state.ApplyMouseDown(point, input.MouseLeft)
|
|
drawSampler()
|
|
if !panelView.selecting {
|
|
t.Fatal("selection gesture did not start")
|
|
}
|
|
if _, err := model.Execute(stdctx.Background(), art.Operation{Kind: "duplicate-frame"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state.BeginFrame()
|
|
state.ApplyMouseUp(mathx.Vec2{X: 455, Y: 355}, input.MouseLeft)
|
|
drawSampler()
|
|
if panelView.selecting || model.Session.Selection != selection || sampler.err == nil || !strings.Contains(sampler.err.Error(), "drawing changed") {
|
|
t.Fatal("a stale selection gesture was published after the document changed")
|
|
}
|
|
if err := ed.UndoAsset(doc); err != nil || model.History.Cursor() != 1 {
|
|
t.Fatalf("could not undo the external frame edit: %v", err)
|
|
}
|
|
sampler.err = nil
|
|
panelView.tool = artLasso
|
|
state.BeginFrame()
|
|
state.ApplyMouseMove(point, mathx.Vec2{})
|
|
state.ApplyMouseDown(point, input.MouseLeft)
|
|
drawSampler()
|
|
state.BeginFrame()
|
|
ed.UI.Begin(state, mathx.Vec2{X: 900, Y: 700}, 1)
|
|
sampler.Draw(&services.Frame{UI: ed.UI, Focused: false}, mathx.Rect{W: 900, H: 700})
|
|
ed.UI.End()
|
|
if panelView.selecting || model.Session.Selection != selection || model.History.Cursor() != 1 {
|
|
t.Fatal("focus loss did not cancel the lasso draft")
|
|
}
|
|
state.BeginFrame()
|
|
state.ApplyMouseUp(point, input.MouseLeft)
|
|
drawSampler()
|
|
if err := model.Session.SetSelection("none", nil, 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Mid-tone UI color values must reach the canonical pixels unchanged; the
|
|
// ColorField's internal linear representation must not darken the stroke.
|
|
panelView.tool = 0
|
|
panelView.color = mathx.ColorRGBA8(127, 128, 200, 255)
|
|
otherPoint := mathx.Vec2{X: 360, Y: 250}
|
|
state.BeginFrame()
|
|
state.ApplyMouseMove(otherPoint, mathx.Vec2{})
|
|
state.ApplyMouseDown(otherPoint, input.MouseLeft)
|
|
drawSampler()
|
|
state.BeginFrame()
|
|
state.ApplyMouseUp(otherPoint, input.MouseLeft)
|
|
drawSampler()
|
|
colored, _, err := model.Capture(frame, art.Albedo)
|
|
if err != nil || model.History.Cursor() != 2 || !bytes.Contains(colored.Pix, []byte{127, 128, 200, 255}) {
|
|
t.Fatalf("canvas did not preserve the selected color bytes: history=%d, error=%v", model.History.Cursor(), err)
|
|
}
|
|
state.BeginFrame()
|
|
drawSampler()
|
|
if !strings.Contains(panelView.canvasStatus, "RGBA8 127 128 200 255") {
|
|
t.Fatalf("canvas did not show the pixel under the cursor: %q", panelView.canvasStatus)
|
|
}
|
|
if err := ed.UndoAsset(doc); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
colored, _, err = model.Capture(frame, art.Albedo)
|
|
if err != nil || !bytes.Equal(colored.Pix, painted.Pix) || model.History.Cursor() != 1 {
|
|
t.Fatal("Undo did not restore the drawing after the colored stroke")
|
|
}
|
|
if disk, err := os.ReadFile(ed.Docs.Abs(doc.Path)); err != nil || !bytes.Equal(initial, disk) {
|
|
t.Fatal("drawing unexpectedly modified the file before Save")
|
|
}
|
|
if err := ed.UndoAsset(doc); err != nil || doc.Dirty() {
|
|
t.Fatalf("shared Undo did not restore saved document: %v", err)
|
|
}
|
|
undone, _, err := model.Capture(frame, art.Albedo)
|
|
if err != nil || bytes.Equal(painted.Pix, undone.Pix) {
|
|
t.Fatalf("Undo did not restore pixels: %v", err)
|
|
}
|
|
if err := ed.RedoAsset(doc); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
redone, _, err := model.Capture(frame, art.Albedo)
|
|
if err != nil || !bytes.Equal(painted.Pix, redone.Pix) {
|
|
t.Fatalf("Redo did not restore painted pixels: %v", err)
|
|
}
|
|
if _, err := model.Execute(stdctx.Background(), art.Operation{Kind: "duplicate-frame", Linked: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(model.Session.Document.Info().Frames) != 2 {
|
|
t.Fatal("linked frame was not added to the native document")
|
|
}
|
|
if err := ed.SaveAsset(doc); err != nil || doc.Dirty() {
|
|
t.Fatalf("Save failed or left the art document dirty: %v", err)
|
|
}
|
|
// This standalone panel fixture has no app-level save notification route.
|
|
if batch, err := pl.Reimport(id); err != nil || len(batch.Failed) != 0 {
|
|
t.Fatalf("fixture reimport failed: %v", err)
|
|
}
|
|
frames := model.Session.Document.Info().Frames
|
|
frameAsset := artsource.StableID(id, artsource.FrameName(frames[1].ID, art.Albedo))
|
|
openedFrame, err := ed.OpenAsset(frameAsset)
|
|
if err != nil || openedFrame != doc || model.Session.Frame != frames[1].ID {
|
|
t.Fatalf("frame AssetRef did not open its owning art document at the selected frame: %v", err)
|
|
}
|
|
if _, err := ed.OpenAsset(id); err != nil || model.Session.Frame != frames[1].ID {
|
|
t.Fatalf("opening the source reset the current frame: %v", err)
|
|
}
|
|
firstAsset := artsource.StableID(id, artsource.FrameName(frames[0].ID, art.Albedo))
|
|
cursor := model.History.Cursor()
|
|
if err := model.Session.BeginGesture("pencil", image.Pt(1, 1), art.Brush{Size: 1, Paint: art.Paint{Color: art.Color{R: 255, A: 255}}}, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := ed.OpenAsset(frameAsset); err != nil || !model.Session.Info().Gesture {
|
|
t.Fatalf("refocusing the current frame disrupted its in-progress gesture: %v", err)
|
|
}
|
|
if _, err := ed.OpenAsset(firstAsset); err == nil || model.Session.Frame != frames[1].ID || !model.Session.Info().Gesture {
|
|
t.Fatal("opening a different frame did not preserve the in-progress gesture")
|
|
}
|
|
model.Session.CancelGesture()
|
|
if _, err := ed.OpenAsset(firstAsset); err != nil || model.Session.Frame != frames[0].ID || model.History.Cursor() != cursor {
|
|
t.Fatalf("frame selection changed document history or failed after cancel: %v", err)
|
|
}
|
|
panel.(workspace.Closer).RequestClose()
|
|
if ed.Docs.AssetDocument(id) != nil || ws.Has(panelID) {
|
|
t.Fatal("closing a saved art document retained its view or model")
|
|
}
|
|
// Open the second frame from the real SpriteRenderer inspector. These input
|
|
// events are simulated at the widget boundary, not an OS/GPU acceptance run.
|
|
sc, _ := lightingFixture(t)
|
|
sceneDoc := ed.Docs.New("scenes/art-inspector.aescene", sc)
|
|
hero := sc.Create(sc.Root(), "Hero")
|
|
sprite := scene.Add[render2d.SpriteRenderer](sc, hero)
|
|
*sprite = render2d.DefaultSpriteRenderer()
|
|
sprite.Sprite.SetAssetRefID(frameAsset)
|
|
sc.Flush()
|
|
beforeScene := sceneio.Save(sc)
|
|
clickSpriteInspector(t, ed, sc, hero)
|
|
reopened := ed.Docs.AssetDocument(id)
|
|
if reopened == nil || reopened.Handler != artHandlerID {
|
|
t.Fatal("inspector click did not open the art source")
|
|
}
|
|
if sceneDoc.History.Cursor() != 0 || !bytes.Equal(beforeScene, sceneio.Save(sc)) {
|
|
t.Fatal("opening the sprite from Inspector changed the scene")
|
|
}
|
|
again := reopened.Binding.Model.(*artedit.Document)
|
|
if again.Session.Frame != frames[1].ID {
|
|
t.Fatal("inspector did not select the referenced animation frame")
|
|
}
|
|
got, _, err := again.Capture(frame, art.Albedo)
|
|
if err != nil || !bytes.Equal(got.Pix, painted.Pix) || len(again.Session.Document.Info().Frames) != 2 || reopened.Dirty() {
|
|
t.Fatalf("reopened source did not retain pixels and timeline: %v", err)
|
|
}
|
|
// Layer icons remain actions on the canonical model and shared history.
|
|
// The read-only variant must not consume a click or mutate that model.
|
|
artPanel := &artPanel{ctx: ed, doc: reopened, model: again, view: &artView{}}
|
|
area := mathx.Rect{W: 240, H: 500}
|
|
clickLayerControl := func(x float32, editable bool) {
|
|
y := ed.UI.Theme.Padding + 7*(ed.UI.Theme.RowHeight+ed.UI.Theme.Spacing) + ed.UI.Theme.RowHeight/2
|
|
point := mathx.Vec2{X: x, Y: y}
|
|
for step := range 3 {
|
|
state.BeginFrame()
|
|
state.ApplyMouseMove(point, mathx.Vec2{})
|
|
if step == 1 {
|
|
state.ApplyMouseDown(point, input.MouseLeft)
|
|
} else if step == 2 {
|
|
state.ApplyMouseUp(point, input.MouseLeft)
|
|
}
|
|
ed.UI.Begin(state, mathx.Vec2{X: 900, Y: 700}, 1)
|
|
artPanel.properties(ed.UI, area, editable)
|
|
ed.UI.End()
|
|
}
|
|
}
|
|
visibleX := area.W - ed.UI.Theme.Padding - 43
|
|
lockX := area.W - ed.UI.Theme.Padding - 13
|
|
clickLayerControl(visibleX, false)
|
|
if !again.Session.Info().Document.Layers[0].Visible || again.History.Cursor() != 0 {
|
|
t.Fatal("read-only layer visibility button changed the document")
|
|
}
|
|
clickLayerControl(visibleX, true)
|
|
if again.Session.Info().Document.Layers[0].Visible || again.History.Cursor() != 1 {
|
|
t.Fatal("layer visibility icon did not update shared history")
|
|
}
|
|
clickLayerControl(lockX, true)
|
|
if !again.Session.Info().Document.Layers[0].Locked || again.History.Cursor() != 2 {
|
|
t.Fatal("layer lock icon did not update shared history")
|
|
}
|
|
if err := ed.UndoAsset(reopened); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := ed.UndoAsset(reopened); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if layer := again.Session.Info().Document.Layers[0]; !layer.Visible || layer.Locked || reopened.Dirty() {
|
|
t.Fatal("Undo did not restore the layer state and saved document")
|
|
}
|
|
if err := ed.Docs.CloseAsset(id, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func clickSpriteInspector(t *testing.T, ed *context.Editor, sc *scene.Scene, node scene.NodeHandle) {
|
|
t.Helper()
|
|
inspectors := inspect.NewRegistry()
|
|
inspectors.SetEditor(ed)
|
|
if err := inspectors.Load(sc.Registry()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inspectorExt := ext.New(ext.Deps{Editor: ed, Schema: sc.Registry(), Inspectors: inspectors})
|
|
if err := inspectorExt.Run("render2d-sprite", spriteInspectorHooks); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
td, ok := sc.Registry().ByName("engine.SpriteRenderer")
|
|
if !ok {
|
|
t.Fatal("sprite type was not registered")
|
|
}
|
|
custom, ok := inspectors.Custom(td.ID)
|
|
if !ok {
|
|
t.Fatal("sprite inspector was not registered")
|
|
}
|
|
nodes := []ids.NodeID{sc.ID(node)}
|
|
defaults := 0
|
|
state := &input.State{}
|
|
drawInspector := func() {
|
|
ed.UI.Begin(state, mathx.Vec2{X: 1000, Y: 500}, 1)
|
|
ed.UI.Region(mathx.Rect{W: 1000, H: 500})
|
|
inspectors.DrawCustom(custom, nodes, func() { defaults++ })
|
|
if got, want := ed.UI.Cursor().Y, ed.UI.Theme.Padding+ed.UI.Theme.RowHeight+ed.UI.Theme.Spacing; got > want {
|
|
t.Fatalf("sprite source action takes more than one row: %v > %v", got, want)
|
|
}
|
|
ed.UI.PopCursor()
|
|
ed.UI.End()
|
|
}
|
|
point := mathx.Vec2{X: 200, Y: ed.UI.Theme.Padding + ed.UI.Theme.RowHeight/2}
|
|
state.BeginFrame()
|
|
drawInspector()
|
|
state.BeginFrame()
|
|
state.ApplyMouseMove(point, mathx.Vec2{})
|
|
state.ApplyMouseDown(point, input.MouseLeft)
|
|
drawInspector()
|
|
state.BeginFrame()
|
|
state.ApplyMouseUp(point, input.MouseLeft)
|
|
drawInspector()
|
|
if defaults != 3 {
|
|
t.Fatal("sprite inspector did not preserve default component controls")
|
|
}
|
|
}
|
|
|
|
func TestSpriteInspectorOpensPreparedImageForEditingWithoutChangingScene(t *testing.T) {
|
|
f := imageAuthoringFixture(t)
|
|
f.ed.UI = core.New(nil)
|
|
sc, _ := lightingFixture(t)
|
|
sceneDoc := f.ed.Docs.New("scenes/image-inspector.aescene", sc)
|
|
hero := sc.Create(sc.Root(), "Hero")
|
|
sprite := scene.Add[render2d.SpriteRenderer](sc, hero)
|
|
*sprite = render2d.DefaultSpriteRenderer()
|
|
sprite.Sprite.SetAssetRefID(f.frames[1])
|
|
sc.Flush()
|
|
beforeScene := sceneio.Save(sc)
|
|
beforeImage, err := os.ReadFile(f.ed.Docs.Abs("assets/surface.png"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
beforeMeta, err := os.ReadFile(f.ed.Docs.Abs("assets/surface.png.meta"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
clickSpriteInspector(t, f.ed, sc, hero)
|
|
doc := f.ed.Docs.AssetDocument(f.albedo)
|
|
if doc == nil || doc.Handler != artPNGHandlerID || doc.Selected != f.frames[1] || !doc.Capabilities.Has(document.EditSource) || doc.Dirty() {
|
|
t.Fatal("the prepared frame did not open as a clean editable PNG draft")
|
|
}
|
|
model, ok := doc.Binding.Model.(*artedit.Document)
|
|
if !ok || !model.Draft {
|
|
t.Fatal("the source did not share an in-memory Art document")
|
|
}
|
|
frames := model.Session.Document.Info().Frames
|
|
if len(frames) != 2 || model.Session.Frame != frames[1].ID {
|
|
t.Fatal("the SpriteRenderer's existing frame did not select its corresponding canvas frame")
|
|
}
|
|
afterImage, err := os.ReadFile(f.ed.Docs.Abs("assets/surface.png"))
|
|
if err != nil || !bytes.Equal(beforeImage, afterImage) || sceneDoc.History.Cursor() != 0 || !bytes.Equal(beforeScene, sceneio.Save(sc)) {
|
|
t.Fatal("opening a prepared image changed the scene or source")
|
|
}
|
|
if afterMeta, err := os.ReadFile(f.ed.Docs.Abs("assets/surface.png.meta")); err != nil || !bytes.Equal(beforeMeta, afterMeta) {
|
|
t.Fatal("opening a prepared image changed its metadata", err)
|
|
}
|
|
}
|