136 lines
9.0 KiB
Markdown
136 lines
9.0 KiB
Markdown
# ADR 0049: Penpot-Led Editor Visual System
|
|
|
|
## Status
|
|
|
|
Accepted
|
|
|
|
## Context
|
|
|
|
Blacksite's editor controls evolved inside individual panels. Even after the material data and
|
|
Inspector composition became shared, local egui styling still produced inconsistent spacing,
|
|
typography, interaction states, and narrow-panel behavior. The Penpot **Inspector Material Slot**
|
|
provides an authored reference for the visual language rather than another implementation-specific
|
|
mock-up.
|
|
|
|
## Decision
|
|
|
|
The Penpot **Inspector Material Slot** is the source of truth for geometry, type roles, control
|
|
heights, radii, spacing, property-column geometry, and interaction states. Color is semantic rather
|
|
than panel-owned: Penpot's Assets / Colors library and `editor::ui::theme::EditorVisualPalette`
|
|
name the same roles, with Blacksite's existing palette as the default. A later Editor Settings slice
|
|
may select or override that palette without changing widgets or material behavior. At the 620 px
|
|
reference width, implementations match the documented geometry. Below that width, controls deliberately
|
|
reflow instead of shrinking, clipping, scrolling horizontally, or hiding required state.
|
|
|
|
Fixed Penpot sections use deterministic geometry models rather than nested egui flow layouts.
|
|
Surface, parameter rows, texture fields, UV groups, and Advanced each receive an owning rectangle;
|
|
every child rectangle is derived from it and clipped to it. The 569 px and 369 px section widths
|
|
encode the exported wide and compact references. A bounded transient mode below 372 px keeps all
|
|
rectangles finite, stacks the row, and collapses low-frequency texture actions into overflow while
|
|
the Inspector restores its 420 px minimum.
|
|
|
|
The reusable implementation lives under `editor::ui::design_system`. It consumes the editor-owned
|
|
semantic palette and owns spacing tokens, Source Sans Pro Regular/Bold typography roles, section chrome, property grids,
|
|
asset and texture fields, segmented controls, switches, status presentation, and the Blacksite
|
|
color picker. Domain widgets consume these primitives instead of copying raw colors or absolute
|
|
Penpot coordinates.
|
|
|
|
Material authoring is the first migrated domain. One action-returning `MaterialsSection` is used by
|
|
primitive, static-mesh, and skinned-mesh inspectors. It owns an ordered list of collapsible
|
|
`MaterialSlotPanel` models, and every returned action carries its stable slot ID. The widgets do not
|
|
query or mutate arbitrary world state.
|
|
Supported material behavior only is shown. Asset identity, health, inheritance, dirty state, and
|
|
read-only state share one geometry, while stable IDs and fingerprints remain in secondary
|
|
diagnostics.
|
|
|
|
The final Penpot component supersedes the earlier v2.2/v2.3 geometry. A 32 px **Materials** heading
|
|
precedes repeated slots. Expanded slot headers are 82 px with a 64 px preview; collapsed headers
|
|
are 52 px with a 32 px preview. A cyan disclosure spine and **PARAMETERS** ownership label inset the
|
|
Surface, Inputs, UV, and Advanced body. The asset identity block owns the single Shader selector. Penpot's former duplicate Surface
|
|
"Shading Model" selector was removed from the component and every state reference in handoff
|
|
v2.3.1. Surface contains blend mode, Separate/ORM selection, and Double Sided. UV
|
|
precedes Advanced with
|
|
an explicit 8 px section gap. Advanced render controls
|
|
that do not yet have renderer contracts are shown only as a clearly disabled preview: they emit no
|
|
actions and create no authored or runtime state.
|
|
|
|
The Material slot header is one assignment interaction, not an identity block beside a second drop
|
|
box. Its whole 596 x 82 px wide reference area accepts valid Material/Instance drops. Preview and
|
|
identity clicks plus the first-class Browse action open the compact current/recent Penpot menu;
|
|
Shader, Locate, Clear,
|
|
and overflow actions are isolated and cannot accidentally open it. The picker returns a stable
|
|
asset reference to the owning Inspector instead of mutating scene or world state.
|
|
|
|
The custom 420 x 350 color popup is a fixed modal centered over its owning Inspector clip, with an
|
|
Inspector-local dim layer rather than application- or viewport-relative placement. Its header,
|
|
mode tabs, wheel and values regions, RGBA byte fields, HSV degree/percentage fields, HEX copy,
|
|
checkerboard alpha control, recents, and footer use the exported Penpot coordinates. The exported
|
|
eyedropper lane remains a tooltip-labelled disabled preview until screen sampling has an owned
|
|
runtime contract. Apply keeps the in-memory dirty value and closes; Cancel, Escape, Close, or
|
|
outside dismissal restores the exact pre-open value. Neither path saves source or schedules derived
|
|
processing.
|
|
|
|
Standard Lit declares seven independently stored inputs but six primary presentation rows.
|
|
`emissive_intensity` is a schema companion of `emissive_color`; the Emissive popup previews and
|
|
restores both values as one interaction without merging their authored/runtime identities. Custom
|
|
shader binding counts likewise count primary rows while rendering companions through their owner.
|
|
|
|
Bounded scalar material inputs use the exported visible-child geometry rather than the narrower
|
|
transparent Penpot parent frame. At the 620 px reference that is a 138.8 px group composed from a
|
|
6 px leading inset, 93.8 px track, 6 px gap, and 33 x 22 numeric field. At 420 px the track expands
|
|
to 160.3 px while the number remains fixed. Expanding the 178 px Advanced preview requests a bounded
|
|
Inspector scroll reveal so its note and lower boundary are visible. The same no-implicit-persistence
|
|
rule applies to sliders, texture assignments, and shader controls.
|
|
|
|
Focused visual iteration uses a standalone `ui_gallery` binary that links the same `editor_ui` and
|
|
`material_schema` production crates as the editor. It owns no project, scene, renderer, or gameplay
|
|
runtime. Exact reference presets, direct Penpot PNG comparison, runtime fixture/theme reload, and a
|
|
managed last-good rebuild/restart loop shorten the edit cycle without creating a second widget
|
|
implementation. Gallery evidence is diagnostic until the same component passes a named full-editor
|
|
interaction scenario.
|
|
|
|
Renderer comparison uses the same rule. The renderer array, renderer identity, property heading,
|
|
indexed material heading, and material slots are production `editor_ui` components. A material
|
|
slot's asset header and parameter disclosure are related but distinct presentation state: renderer
|
|
arrays may keep the complete asset/action header visible while the expensive parameter body is
|
|
collapsed. Empty assignments use a first-class empty presentation instead of reusing pending or
|
|
failed thumbnail state.
|
|
|
|
Inspector overflow is part of the shared visual contract. A floating scrollbar keeps component
|
|
width stable as content crosses the vertical overflow threshold, and every nested component clip is
|
|
an intersection with the owning Inspector body. Ready texture fields use the typed thumbnail as
|
|
their sole leading identity; generic image glyphs are reserved for empty states.
|
|
|
|
Standard Lit exposes one shared UV Offset/Tiling transform through the same schema-driven input
|
|
document. The renderer maps it to Bevy StandardMaterial UV transforms and to every Surface ABI
|
|
texture lane. Material Instances may override it sparsely; resetting an instance restores its base.
|
|
|
|
Penpot's `CODEGEN MAP` and handoff annotations are design documentation. Runtime egui layout remains
|
|
responsive and semantic; generated SVG or code is not copied into production. Reviewed numeric
|
|
geometry is represented by small testable layout models instead of ad hoc child flow or generated
|
|
widget trees.
|
|
The same release slice applies the active Inspector Header, Array Header, renderer-panel, asset
|
|
field, vector, selection, and overlay components across the Inspector. Built-in component cards
|
|
dispatch through the registry and share one clipped actor-body scroll owner; domain behavior stays
|
|
outside the visual primitives.
|
|
|
|
## Consequences
|
|
|
|
- New editor UI has one reviewed visual vocabulary rather than panel-local approximations.
|
|
- Theme colors have one runtime owner and one design-side asset vocabulary; the material panel does
|
|
not carry a private blue/teal palette.
|
|
- The 620 px and 420 px geometries are deterministic and unit-testable. The Inspector has a 420 px
|
|
floor and one actor-body scroll region; material slots never introduce nested scrolling.
|
|
- Unsupported prototype controls are omitted until their runtime contracts exist.
|
|
- The explicitly approved Advanced preview is the sole exception: it is disabled, muted, and
|
|
labelled as planned work rather than functioning authoring state.
|
|
- Native acceptance compares rendered geometry, typography, states, and interaction behavior with
|
|
the Penpot reference; compile-only evidence is insufficient.
|
|
- Source Sans Pro and its SIL Open Font License are bundled with the editor.
|
|
- ADR 0048 continues to own composition and module-size policy. This ADR owns visual tokens,
|
|
responsive component geometry, and interaction presentation.
|
|
|
|
Related decisions: [ADR 0035](0035-shared-material-assets-and-renderer-slots.md),
|
|
[ADR 0047](0047-editor-authored-asset-documents.md), and
|
|
[ADR 0048](0048-modular-editor-composition-and-debt-ratchet.md).
|