Files
sanspie 69d19e501f Stage 7: documentation, help, playground; templates, aego new/bootstrap/icons, launcher
Documentation: engine/docs assembles one registry from the script
bindings, the schema, module docs and module-owned script paths; problems
are reported as warnings at build time. Module registries record doc
sources and script module owners; diag.Codes lists every diagnostic code.

Project bootstrap: project.defaultSpace ("2d"/"3d"), EnsureDirs creates
the default folders when the editor opens a project (reported in the
console), prefabs/ is a pipeline root. Native builds drop -trimpath so
binding locations resolve inside native/.

Editor files: create scene/prefab/script from menus, move/rename/duplicate
/delete with undo, script package retargeting on move, open scenes and
script panels follow renames, Save As / Rename Scene prompts, scene
picker, startup scene. Scoped shortcuts fall back to the global command
when disabled; Save As is Ctrl+Shift+S, Save All Ctrl+Alt+S.

Script editor: Go to Implementation (stub → native/ source), host
definitions always resolve to stubs, bracket match/mismatch states,
rainbow brackets (persisted), mono code font (ui-mono atlases, per-command
fonts in draw.List, PushFont/PopFont restored by Mark/Restore).

Tests: langsvc definition/implementation, bracket states, dispatch
fallback, script renames, font stack, editor file-op integration, golden
mono glyph and bracket references; depcheck rule for engine/docs.

Docs pipeline: engine, render2d and script/engine ship their own res/docs;
keyword hover text comes from the registry through manifest.Keywords;
project script symbols join the registry; aego docs export|check|build|
serve, --tool docs in native binaries, aego check --explain; app/docstool
and the tools/gendocs module (goldmark) build the static site.

Playground: app/playground + cmd/aego-playground (js/wasm, 3.3 MB gzip)
compile, highlight and run scripts in the browser; run blocks in the
language docs become examples. engine/builtin/headless is the module set
without render3d; workspace.Config.FS; StubFor/StubPaths; backends and the
script importer drop out of js builds.

VM: one frame pointer in the interpreter loop, direct host calls, panic
recovery once per run instead of per host call (Update2k -19%,
Schedule10k -24% against the previous head). bench-build/compare/profile
run pre-built, pinned, interleaved binaries.

Editor: structural bracket levels and current-block highlight, indent
guides, occurrence highlighting with write marks, multi-format clipboard
(copy/cut/paste assets, paste images as sprites or textures). gl33 probes
a renderable depth format and reports framebuffer failures by stage.

7 E:
- project templates empty/2d/2.5d/3d, app/projects as the single creation path (D148)
- launcher and recent projects (D149)
- orthographic Camera3D and Scene View (D150)
- reference states in the Inspector, ids.Short (D151)
- sceneio remaps node references when loading without KeepIDs (D152)
- exported ComponentRef<T> (D153)
- aego icons via module.Audit (D146)
2026-10-13 01:08:56 +03:00

135 lines
3.3 KiB
Go

package clip
import (
"bytes"
"encoding/binary"
"image"
"image/color"
"os"
"path/filepath"
"testing"
)
func pattern() *image.NRGBA {
img := image.NewNRGBA(image.Rect(0, 0, 3, 2))
for y := 0; y < 2; y++ {
for x := 0; x < 3; x++ {
img.SetNRGBA(x, y, color.NRGBA{R: uint8(40 * x), G: uint8(90 * y), B: 200, A: 255})
}
}
return img
}
func TestImageAndFilesRoundTrip(t *testing.T) {
src := pattern()
path := filepath.Join(t.TempDir(), "a b#1.png")
var m Memory
if !SetImage(&m, src, []string{path}) {
t.Fatal("set image failed")
}
if !HasImage(&m) || !HasFiles(&m) {
t.Fatal("image and files must both be offered")
}
got, ok := Image(&m)
if !ok || got.Rect != src.Rect || !bytes.Equal(got.Pix, src.Pix) {
t.Fatal("pixels changed through the clipboard")
}
files, ok := Files(&m)
if !ok || len(files) != 1 || files[0] != path {
t.Fatalf("files = %v", files)
}
if m.Text() != path {
t.Fatalf("text = %q", m.Text())
}
}
func TestURIListParsing(t *testing.T) {
data := []byte("# comment\r\nfile:///tmp/x%20y.png\r\nhttp://example.com/a.png\r\nfile://remote/z.png\r\nfile://localhost/tmp/w.png\r\n")
got := ParseURIList(data)
want := []string{filepath.FromSlash("/tmp/x y.png"), filepath.FromSlash("/tmp/w.png")}
if len(got) != len(want) || got[0] != want[0] || got[1] != want[1] {
t.Fatalf("parsed = %v", got)
}
gnome := ParseURIList(GnomeFiles([]string{filepath.FromSlash("/tmp/a.png")}))
if len(gnome) != 1 || gnome[0] != filepath.FromSlash("/tmp/a.png") {
t.Fatalf("gnome = %v", gnome)
}
}
func TestTextPaths(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "x.png")
if err := os.WriteFile(f, []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
var m Memory
m.SetText(f + "\n")
if files, ok := Files(&m); !ok || len(files) != 1 || files[0] != f {
t.Fatalf("existing path not taken: %v", files)
}
for _, s := range []string{"hello", filepath.Join(dir, "missing.png"), "relative/x.png"} {
m.SetText(s)
if _, ok := Files(&m); ok {
t.Fatalf("%q taken as a file", s)
}
}
}
func bmp(w, h, bpp int, topDown bool, px func(x, y int) [4]byte) []byte {
le := binary.LittleEndian
stride := (w*bpp/8 + 3) &^ 3
size := 54 + stride*h
b := make([]byte, size)
b[0], b[1] = 'B', 'M'
le.PutUint32(b[2:], uint32(size))
le.PutUint32(b[10:], 54)
le.PutUint32(b[14:], 40)
le.PutUint32(b[18:], uint32(w))
hh := int32(h)
if topDown {
hh = -hh
}
le.PutUint32(b[22:], uint32(hh))
le.PutUint16(b[26:], 1)
le.PutUint16(b[28:], uint16(bpp))
for y := 0; y < h; y++ {
row := h - 1 - y
if topDown {
row = y
}
for x := 0; x < w; x++ {
c := px(x, y)
o := 54 + row*stride + x*bpp/8
b[o], b[o+1], b[o+2] = c[2], c[1], c[0]
if bpp == 32 {
b[o+3] = c[3]
}
}
}
return b
}
func TestBMP(t *testing.T) {
px := func(x, y int) [4]byte { return [4]byte{byte(10 + x), byte(20 + y), 30, 0} }
for _, tc := range []struct {
bpp int
top bool
}{{24, false}, {24, true}, {32, false}, {32, true}} {
img, err := DecodeBMP(bmp(3, 2, tc.bpp, tc.top, px))
if err != nil {
t.Fatalf("%+v: %v", tc, err)
}
for y := 0; y < 2; y++ {
for x := 0; x < 3; x++ {
c := img.NRGBAAt(x, y)
if c.R != byte(10+x) || c.G != byte(20+y) || c.B != 30 || c.A != 255 {
t.Fatalf("%+v: pixel %d,%d = %+v", tc, x, y, c)
}
}
}
}
if _, err := DecodeBMP([]byte("BM")); err == nil {
t.Fatal("truncated bmp accepted")
}
}