Nine-slice and sort line on pixel art and vector art (#308)

Both settings used to belong to the texture frame alone. Now a pixel
art or vector art asset carries them too, so the pages say so.

- Textures, Pixel-Art, Vector-Art: a section "Nine-slice and sort
  line" each. The drawing tabs show the lines but do not drag them.
- Architecture: PictureCut, cutTarget.ts and cutSection.ts.
- Development: e2e sum 1732.
admin committed 2026-10-03 19:54:13 +02:00
1 parent 6fec7ee674
commit a8a6580a34
5 files changed
+41 -6

No files matched your search

+3 -3
@@ -119,7 +119,7 @@ web/index.html Vite source page (dev) index.html built page
web/play.html a released game (#266) play.html built page (generated, served)
src/main.ts, play.ts the two entries; page.ts is the canvas both are (the WebGL context, root, resize)
src/core/model/ Project (object holders), Asset + AssetTypes, SceneObject (and what a list calls one:
`objectName`, `groupName`), Properties (value types, and the walks over a nested value: `mapNested`, a struct field taken out or renamed), hierarchy, walk (generic parent/child walks), instance (container content resolved from its template), usages (where an asset is used), container (the cycle guard), placement (what may be dropped where), texture (frames, the slice grid, a frame's collision outlines), frameOutline (#193: a frame's outlines as a collider wears them, centred, a pixel to a unit, cut into pieces once per drawn list), traceCollision (#161: a frame's outlines read off its picture — the pixels from an alpha threshold on are solid, each 4-connected island is walked round along the pixel edges and cut down to a few corners without ever crossing itself; crumbs below 1 % of the frame drop out), generated (pictures that are drawn rather than uploaded, #185: the key `gen:<assetId>@<revision>`, the register of readers that tell the picture path an asset's canvas size and revision, and the `fingerprint` pixel art and vector art make their revision with), vectorart (#129: the canvas as a property, which ring is worth drawing, and the revision as a fingerprint of the drawing rather than a counter, a texture fill's picture included) + vectorPath (what a shape draws, as plain commands: closed rings fill, every ring strokes, a segment is a line while neither of its ends carries a handle; a paint resolved to sorted stops or the picture it names) + vectorPaint (#186: gradients and texture fills — where a paint lies when it is switched on, the colour between two stops, adding, moving and removing a stop, a paint moved with its shape, and the rule that a texture fill takes uploaded pictures only) + vectorBoolean (#187: union, subtract, intersect and exclude on the sampled outlines, each shape read by the even-odd rule; the result rings in the order Pixi's hole detection needs, every outline followed by its holes and preceded by the islands inside them) + vectorCurves (#208: every sampled point remembers its cubic and its `t`, so a run of result points along one cubic becomes that cubic again, cut with de Casteljau), pixelart (#130: the palette a picture is painted in, how the layers of a frame come together, the sheet of all frames, a frame id read as a cut of it, and the revision) + pixelCel (the file of one layer on one frame, and its SHA-256 name worked out in the browser) + pixelDraw (what the tools do to the indices: brush, line, rectangle, ellipse, fill, resize about an anchor, merge, a palette colour taken out) + pixelEdit (every edit of layers, frames and cels as a function from one state to the next), polygon (an outline checked, cut into convex pieces and clipped to a rectangle), tile (the three kinds and their block shapes, reading a tile's pictures, the animation step), autotile (which quarter tile a neighbourhood asks for, the 47/16 tiles a block produces, 1 for a plain tile), tilemap (the cells of a layer, which neighbours a cell is joined to, the cells a stroke or a rectangle covers), tilemapCollision (the solid cells of a map, drawn in or out by their tile's collision scale and merged into rectangles, one group per one-way angle and per surface (`tileMaterials`, #193), the outlined cells as polygons — an autotile's outlines cut into its quarters and pieced together per cell like its picture —, where one sits in the world, and which cells of a placed map a world rectangle can reach, in cells (`cellsWithin`, #184)), tilemapMemo (what a map's cells work out to, kept while they hold the same: a version per cell map compared cell by cell, since strokes paint in place), animation (which frame at time t, a row's value there, what a row may aim at, the players on an object; KeyRef and its lookups) + animationPreview (the root at a frame, written into copies), curve (#247: a drawn easing evaluated at t — each segment a cubic solved for t by bisection, held to its rules by `sanitizedCurve`: ends at 0 and 1, no point overtaking another, no handle past its neighbour — and which keys and `Lerp` nodes use one) + curvePresets (the built-in eases traced as curves, a segment halved only where it strays), graphLookup (what a graph sees of the project: behaviors to run, state machines to read, curves to ease along), structTypes (#253: Struct assets as types — every typed struct variable, struct field and `Pack`/`Expand` found and brought in line with its asset, a renamed field carried through every value first, the cycle rule), Scope (how long a shared value lives: per instance, one for the run, or one per scene and rebuilt with every scene that loads, #164 — for a state machine's current state and for the values of a Variables asset), collisionLayers (which colliders may meet: the project's named layers, a collider's layer and mask, and the one rule `layersMeet` the engine, Solids and the event pair search all answer by), renderLayers (#302: which cameras see an object — tLine truncated
`objectName`, `groupName`), Properties (value types, and the walks over a nested value: `mapNested`, a struct field taken out or renamed), hierarchy, walk (generic parent/child walks), instance (container content resolved from its template), usages (where an asset is used), container (the cycle guard), placement (what may be dropped where), texture (frames, the slice grid, a frame's collision outlines, and the nine-slice and sort line of any picture, #308), cutTarget (which picture a nine-slice or sort line is set on: a texture frame, or pixel art and vector art as a whole, #308), frameOutline (#193: a frame's outlines as a collider wears them, centred, a pixel to a unit, cut into pieces once per drawn list), traceCollision (#161: a frame's outlines read off its picture — the pixels from an alpha threshold on are solid, each 4-connected island is walked round along the pixel edges and cut down to a few corners without ever crossing itself; crumbs below 1 % of the frame drop out), generated (pictures that are drawn rather than uploaded, #185: the key `gen:<assetId>@<revision>`, the register of readers that tell the picture path an asset's canvas size and revision, and the `fingerprint` pixel art and vector art make their revision with), vectorart (#129: the canvas as a property, which ring is worth drawing, and the revision as a fingerprint of the drawing rather than a counter, a texture fill's picture included) + vectorPath (what a shape draws, as plain commands: closed rings fill, every ring strokes, a segment is a line while neither of its ends carries a handle; a paint resolved to sorted stops or the picture it names) + vectorPaint (#186: gradients and texture fills — where a paint lies when it is switched on, the colour between two stops, adding, moving and removing a stop, a paint moved with its shape, and the rule that a texture fill takes uploaded pictures only) + vectorBoolean (#187: union, subtract, intersect and exclude on the sampled outlines, each shape read by the even-odd rule; the result rings in the order Pixi's hole detection needs, every outline followed by its holes and preceded by the islands inside them) + vectorCurves (#208: every sampled point remembers its cubic and its `t`, so a run of result points along one cubic becomes that cubic again, cut with de Casteljau), pixelart (#130: the palette a picture is painted in, how the layers of a frame come together, the sheet of all frames, a frame id read as a cut of it, and the revision) + pixelCel (the file of one layer on one frame, and its SHA-256 name worked out in the browser) + pixelDraw (what the tools do to the indices: brush, line, rectangle, ellipse, fill, resize about an anchor, merge, a palette colour taken out) + pixelEdit (every edit of layers, frames and cels as a function from one state to the next), polygon (an outline checked, cut into convex pieces and clipped to a rectangle), tile (the three kinds and their block shapes, reading a tile's pictures, the animation step), autotile (which quarter tile a neighbourhood asks for, the 47/16 tiles a block produces, 1 for a plain tile), tilemap (the cells of a layer, which neighbours a cell is joined to, the cells a stroke or a rectangle covers), tilemapCollision (the solid cells of a map, drawn in or out by their tile's collision scale and merged into rectangles, one group per one-way angle and per surface (`tileMaterials`, #193), the outlined cells as polygons — an autotile's outlines cut into its quarters and pieced together per cell like its picture —, where one sits in the world, and which cells of a placed map a world rectangle can reach, in cells (`cellsWithin`, #184)), tilemapMemo (what a map's cells work out to, kept while they hold the same: a version per cell map compared cell by cell, since strokes paint in place), animation (which frame at time t, a row's value there, what a row may aim at, the players on an object; KeyRef and its lookups) + animationPreview (the root at a frame, written into copies), curve (#247: a drawn easing evaluated at t — each segment a cubic solved for t by bisection, held to its rules by `sanitizedCurve`: ends at 0 and 1, no point overtaking another, no handle past its neighbour — and which keys and `Lerp` nodes use one) + curvePresets (the built-in eases traced as curves, a segment halved only where it strays), graphLookup (what a graph sees of the project: behaviors to run, state machines to read, curves to ease along), structTypes (#253: Struct assets as types — every typed struct variable, struct field and `Pack`/`Expand` found and brought in line with its asset, a renamed field carried through every value first, the cycle rule), Scope (how long a shared value lives: per instance, one for the run, or one per scene and rebuilt with every scene that loads, #164 — for a state machine's current state and for the values of a Variables asset), collisionLayers (which colliders may meet: the project's named Line truncated
src/core/properties/ PropertySchema, kinds (one row per value kind), listItems (an item's kind, read off its value), resolve (the only override merge), deviation (what is gold), collider/camera/bounds geometry, clip (the region clip areas cut each object to, a convex polygon, #260) (with the side a one-way collider blocks from, and the rectangle around a shape a broad phase looks at first, #184), convex (capsules and polygons: rounded convex pieces against each other), ray (where a ray meets a shape, in double precision), rigidbody (what the engine reads off a rigidbody, a collider's material and the scene), joint (what it reads off a joint: its settings, where its far end is, its length and a hinge's limits, #154), aspect, enabled, editorRun, assetRef/textureRef (how a reference reads), declaredRows (the row a Property node declares in a shader or particle graph, and the params that node has in both)
src/core/util/ list (take out, put back and move an item — what a command's undo is made of), vec (dot, the closest point on a segment — shared by the collider maths, the outline hit area and the polygon grips; a cubic Bezier at `t` and cut in two at `t` — shared by vector art, its Boolean operations and the curve grips), color (`mixColor`, channel by channel — keys of an animation, stops of a gradient, the particles' Mix colour), recorder (#253: assignments taken back last first — the undo of an edit whose reach is not known in advance)
src/core/commands/ undoable commands (pixelArtCommands: one command runs any pixel art edit and keeps the state before it, #130), CommandStack, SetBagValueCommand base, RenameCommand base (undone by renaming back: variable, struct field, port), MoveCommand base (undone by moving back: tile picture, tilemap layer, vector shape), vectorArtCommands (#129: the shapes of a drawing, one gesture one command), one import command shape for texture/font/sound, require.ts (validated lookups, and what an instance may not change), naming.ts
@@ -275,7 +275,7 @@ src/editor/inspector/ Inspector panel, host (what a section builds into, and `e
every variable), shapeSection (the shapes of a vector drawing: the list, the four order
buttons and the style of the picked one, #129; Fill type and Line type with the stop bar,
the picked stop's rows or the texture, #186), portSection, aspectAction,
objectQueries/vectorRows/parentRow
objectQueries/vectorRows/parentRow, cutSection (the Nine-slice and Sorting rows, shared by the frame and the drawing inspectors, #308)
src/editor/panels/ AssetPanel, assetGroups (groups by type and their order, #176), assetIcons (one SVG per
asset type in icons/, shared by the panel, the + menu, the tabs and Scene objects,
plus the List, Game, Plugins and User management tabs' own four, which stand for no asset),
@@ -298,7 +298,7 @@ src/editor/graph/ GraphEditor (header, breadcrumb), GraphCanvas and what it
AnnotationView + annotationDrag (groups and comments: what a drag carries, each thing moved once),
SnapToggle (the magnet every tab that snaps carries, the vector art tab included, #194)
src/editor/container/ ContainerEditor (header), ContainerCanvas (a SceneViewport on a template)
src/editor/texture/ SheetCanvas (the shell of the Texture, Pixel art and Vector art canvases: ground, clip, hint, a middle or Shift drag that pans, wheel zoom, Fit and the view each document keeps), TextureEditor (header), TextureCanvas (picture + frames), SliceDialog (a FormDialog), GenerateCollisionDialog (#161: the rows, and a summary that traces while the numbers change; the texture library hands it the sheet's pixels), sheetView (the grid maths),
src/editor/texture/ SheetCanvas (the shell of the Texture, Pixel art and Vector art canvases: ground, clip, hint, a middle or Shift drag that pans, wheel zoom, Fit and the view each document keeps, and the dashed nine-slice and sort lines of a drawing, #308), TextureEditor (header), TextureCanvas (picture + frames), SliceDialog (a FormDialog), GenerateCollisionDialog (#161: the rows, and a summary that traces while the numbers change; the texture library hands it the sheet's pixels), sheetView (the grid maths),
ImagePicker + imageGrid (the "+ → Texture" dialog), checker (the transparency board)
src/editor/pixelart/ PixelArtEditor (header: Resize…), PixelArtCanvas (one frame composed as the picture path composes it,
whole zoom steps, grid, onion skin and tile preview; a stroke paints a copy and becomes one command),
+1 -1
@@ -18,7 +18,7 @@ php tests/php/accounts.test.php # the API's accoun
# has and the web server's container has not (SKIP there)
php tests/php/snapshots.test.php # the snapshot store (#231), the same way
php tests/php/releases.test.php # the release store (#266), the same way
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1731 editor checks, screenshots in /tmp/gm-e2e-*.png
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1732 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result
+17 -2
@@ -16,8 +16,23 @@ new width and height and for the **anchor**: nine buttons for the corners, the e
The picture keeps its place at that anchor, and whatever sticks out on the far side is cut off. A
resize is one undo step, for every layer on every frame at once.
The inspector shows two rows: **Palette** (which palette the picture is painted in) and **Filter**,
**Nearest** by default so the pixels stay sharp when a sprite scales them up.
The inspector shows three rows: **Palette** (which palette the picture is painted in), **Filter**,
**Nearest** by default so the pixels stay sharp when a sprite scales them up, and **Nine-slice
scale** (see [below](#nine-slice-and-sort-line)).
## Nine-slice and sort line
A sprite wearing pixel art can be nine-sliced and given a sort line, exactly as a sprite wearing a
texture frame ([Textures](Textures#nine-slice--a-frame-that-keeps-its-corners)). The settings sit
on **the asset**, under *Nine-slice* and *Sorting* in its inspector, in canvas pixels. One set
serves **every frame**: the frames share the canvas size, and the frames of one animation want the
same corners and the same feet.
- The tab draws the borders that are set as dashed lines and the sort line in yellow. They are not
dragged there, the painting tools own the canvas. Type the numbers in the inspector.
- With two frames or more, the asset itself names the strip of all frames side by side. That strip
is not sliced, only a single frame is. With one frame the asset and its frame are the same picture.
- Resizing the canvas keeps the numbers, and they are held inside the new size when drawn.
## Colours and the palette
+7
@@ -167,6 +167,11 @@ drawn twice as big beside a pattern at its old size reads as two different pictu
sprite narrower than its borders applies to the scaled size: two 32-unit borders on a 20-unit box
become two of 10.
**Drawings are sliced the same way (#308).** [Pixel art](Pixel-Art#nine-slice-and-sort-line) and
[vector art](Vector-Art#nine-slice-and-sort-line) carry the same borders, fill and sort line, set
on the asset as a whole and measured in canvas pixels, and they have a **Nine-slice scale** row of
their own. It is the same rule and the same inspector rows, only the target differs.
## Sort line — where a figure stands
On a tilemap layer sorted by Y (see [Tilemaps](Tilemaps#sorting-by-y)) a sprite is sorted by the
@@ -183,6 +188,8 @@ box higher up, so a line 6 px up a 24 px frame lifts a 48-unit sprite by 12 unit
so grab it by the knob; a line inside the frame can also be dragged directly. The inspector has
the same number under *Sorting*. One drag or one typed value is one undo step.
- 0 carries no key in the saved document. It only matters on a Z with a layer sorted by Y.
- Pixel art and vector art have the same row on the asset (#308). A drawing without frames is
measured against its whole canvas.
**Fitting the sprite to the picture.** The *Width* and *Height* rows of a sprite carry a ↔ / ↕
button offering two things: **Fit to 64 x 48** sets that one side from the other and the picture's
+13
@@ -307,6 +307,19 @@ Two differences to an uploaded picture, both from the picture path: a drawing ha
(frames are rectangles cut out of a sheet, and there is no sheet), and it is **not stored** as an
image — the project file holds the shapes, and the picture is rasterised again after a reload.
## Nine-slice and sort line
A drawing can be nine-sliced and given a sort line, exactly as a texture frame
([Textures](Textures#nine-slice--a-frame-that-keeps-its-corners)), for a panel or a button drawn
with curves (#308). The settings sit on **the asset**, under *Nine-slice* and *Sorting* in its
inspector, in **canvas pixels**: a 16 px border of a 256 canvas is a sixteenth of it, whatever
resolution the picture is rasterised at. **Nine-slice scale** is a row of the asset, as on a
texture.
The tab draws the borders that are set as dashed lines and the sort line in yellow, but they are
not dragged there: the arrow and the pen own the canvas. Type the numbers in the inspector.
Changing the canvas size keeps the numbers, and they are held inside the new size when drawn.
## Known limits
- **A texture fill always repeats.** Pixi turns a non-repeating picture fill into a repeating one,