280 lines
8.8 KiB
Markdown
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) 1fr 1fr)
|
|
: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
|
|
```
|