ebox/docs/user/ebox-user-guide.en.md

280 lines
8.8 KiB
Markdown

# Ebox user guide
[中文](ebox-user-guide.zh.md)
Ebox is the low-level Text/Box layout and buffer-rendering package. This guide
uses one public author grammar and keeps framework-integration details separate.
See the [API reference](ebox-api-reference.en.md) for the complete function
inventory.
## 1. Load Ebox
```elisp
(require 'ebox)
```
Loading Ebox does not create a buffer, enable a mode in the current buffer, or
build Rust.
## 2. Learn one author model
An Ebox document contains Text and Box nodes. The author grammar has exactly
seven entries:
| Entry | Meaning |
| --- | --- |
| `"text"` | Short form for one Text node. |
| `(text ... "text")` | Text with explicit text properties. |
| `(box ... CHILD...)` | A normal visual Box. |
| `(row ... CHILD...)` | A Box with simple horizontal child layout. |
| `(column ... CHILD...)` | A Box with simple vertical child layout. |
| `(flex ... CHILD...)` | A Box with Flex child layout. |
| `(grid ... CHILD...)` | A Box with Grid child layout. |
Children are always nested directly. A layout form is a Box with a selected
child-layout algorithm, not a different kind of visual object.
```elisp
(defvar ebox-guide-input
(ebox-build
'(column :padding (1 2)
:border (1 solid "#8A93A6")
(text :color "#263244" "Research notes")
(row :item-gap 1
(box :id "status" :background-color "#F4F6FB" "Inbox")
(box :background-color "#EEF2FF" "Archive")))))
```
`ebox-build` returns an opaque `CanonicalEboxInput`. It keeps the canonical
forest and its source generation together. Pass that value unchanged to the
render and publication functions; ordinary author code does not extract or
reassemble its internal nodes.
Use `box` with geometry but no child when an empty rectangular area is needed.
No extra node type is necessary.
## 3. Choose the layout that states the intent
Use `box` for ordinary content, `row` or `column` for direct one-axis
composition, `flex` when free space or wrapping matters, and `grid` for
two-dimensional tracks or explicit placement.
### Row and column
`row` and `column` accept `:item-gap` and `:cross-align`. Their children remain
ordinary Text or Box nodes.
```elisp
(ebox-build
'(row :item-gap 2 :cross-align center
(box :width 12 "Left")
(box :width 12 "Right")))
```
### Flex
Flex container properties belong to `flex`. Participation properties belong
directly to a child `box` because they describe the parent-child relationship.
```elisp
(ebox-build
'(flex :width (480)
:flex-flow (row wrap)
:gap (1 (12))
(box :flex (1 1 auto) "Primary")
(box :flex-grow 2 "Secondary")))
```
### Grid
Grid placement is one-based. Tracks may be fixed, `auto`, fractional,
`minmax`, or `repeat` values. Placement properties also belong directly to a
child `box`.
```elisp
(ebox-build
'(grid :width (640)
:grid-template-columns ((200) (fr 1) (fr 1))
:grid-template-rows (1 1)
:gap (1 (12))
(box :grid-column (1 :span 3) "Header")
(box :grid-column 1 :grid-row 2 "Navigation")
(box :grid-column 2 :grid-row 2 "Main")
(box :grid-column 3 :grid-row 2 "Aside")))
```
## 4. Use geometry and paint properties
Horizontal numbers are character columns; a one-element list such as `(240)`
is a pixel width. Vertical numbers are lines. Text accepts only font,
foreground/background, and text-decoration properties. Padding, margin,
border, size, `:outer`, overflow, visibility, and wrapping policy belong only
to Box. Font and color on Box may feed inherited Text facts, but never give
Text Box geometry.
```elisp
(ebox-build
'(box :width (420)
:padding (1 2)
:margin (0 1)
:border (1 solid "#8A93A6")
:color "#263244"
:background-color "#FFFFFF"
"A readable panel"))
```
Use `:outer inline` or `:outer block` to state how a Box participates in its
parent. The child-layout algorithm still comes from the form name.
## 5. Render and publish
`ebox-render` returns propertized text without changing a live buffer.
`ebox-render-to-buffer` mounts a retained TP surface and returns its buffer.
Both functions, and `ebox-display-buffer`, accept the opaque value returned by
`ebox-build`.
```elisp
(ebox-render ebox-guide-input)
(ebox-render-to-buffer "*Ebox Guide*" ebox-guide-input)
```
Build a fresh canonical input and use `ebox-commit` for an atomic update:
```elisp
(ebox-commit
"*Ebox Guide*"
(ebox-build
'(column :padding (1 2)
(text "Updated notes")
(box :key body "The new input is caller-owned."))))
```
Validation, rendering, or publication failure leaves the previous buffer and
runtime state intact. `ebox-buffer-update-report` returns a defensive copy of
the last successful update report.
## 6. Query and update a mounted surface
`:id`, `:class`, and `:key` are author metadata. Selectors use ECSS semantics.
Resolve an `:id` to an opaque, surface-scoped handle before a direct update:
```elisp
(let ((handle (ebox-region-resolve "*Ebox Guide*" "status")))
(ebox-region-update handle :color "#166534"))
```
`ebox-selector-query-buffer` returns document-ordered matches from a mounted
buffer. `ebox-selector-update-buffer` applies one style update to all editable
matches. Numeric region ids are diagnostic render metadata, not stable update
handles.
## 7. Resize and scroll
A finite height plus `:overflow scroll` creates a scroll window. Ebox buffer
mode installs keyboard and wheel commands that route through the innermost
scroll owner before falling back to ordinary Emacs scrolling.
```elisp
(ebox-build
'(box :id log :width (420) :height 8 :overflow scroll
"line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9"))
```
Visible mounted buffers follow their display window. Integrations may apply an
explicit viewport with:
```elisp
(ebox-rerender-buffer-with-context
(get-buffer "*Ebox Guide*") 800 30)
```
## 8. Standalone `.ebox` files
A `.ebox` file contains one unquoted structural form:
```elisp
(column :padding '(1 2)
(text :color "#263244" "Title")
(box :width '(240) "Body"))
```
The sibling `ebox-playground` evaluates property expressions before calling
`ebox-build`. Quote list and symbol constants in a `.ebox` file, as shown
above. When calling `ebox-build` directly, pass inert data as in the earlier
examples.
## 9. Typed integration API
Frameworks that already normalize author input may bypass the list DSL. This
is an integration API, not a second author grammar. One source builder owns
the complete source generation. Each TextNode or BoxNode receives an opaque
handle from that builder and node-owned facts projected from the same
normalized declarations. The framework then seals the builder and transports
the forest and source index together as one `CanonicalEboxInput`.
```elisp
(let* ((builder (ebox-source-builder-create))
(root-declarations nil)
(left-declarations nil)
(right-declarations nil)
(root-handle
(ebox-source-builder-bind
builder :declarations root-declarations))
(left-handle
(ebox-source-builder-bind
builder :declarations left-declarations))
(right-handle
(ebox-source-builder-bind
builder :declarations right-declarations))
(left
(ebox-text-create
:value "Left"
:source-handle left-handle
:owned-facts
(ebox-canonical-facts-from-declarations
'text left-declarations)))
(right
(ebox-text-create
:value "Right"
:source-handle right-handle
:owned-facts
(ebox-canonical-facts-from-declarations
'text right-declarations)))
(root
(ebox-box-create
:layout (ebox-row-layout-create
:item-gap 1 :cross-align 'center)
:children (list left right)
:source-handle root-handle
:owned-facts
(ebox-canonical-facts-from-declarations
'row root-declarations))))
(ebox-canonical-input-create
(list root)
(ebox-source-builder-finish builder)))
```
`ebox-source-builder-create`, `ebox-source-builder-bind`,
`ebox-source-builder-finish`, `ebox-canonical-facts-from-declarations`, the
typed node/layout constructors, and `ebox-canonical-input-create` belong to
this integration boundary. Here `:layout`, `:children`, `:source-handle`, and
`:owned-facts` are evaluated constructor fields. They are not author
properties and do not extend the seven-entry grammar.
## 10. Optional native reflow and verification
The Rust module is optional and Ebox never builds it while loading:
```elisp
(ebox-native-status)
(ebox-native-build)
```
From the repository root:
```sh
make docs-contract-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
make dsl-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
```