Add layer examples and tests, document usage in README
This commit is contained in:
parent
6cd5a67a35
commit
834c05f18b
@ -21,3 +21,5 @@ For size, unit, keyword, or sizing-function demonstration work, maintain `exampl
|
|||||||
Keep necessary business functions and variables in a same-basename `.el` file beside the `.ebox` layout. The generic runner loads that exact companion source, when present, before every preview evaluation, including `C-c C-c` and `ebox-playground-open-file`. Keep the `.ebox` focused on layout and its data references; do not add helpers merely to hide repeated property lists. Reload state according to ordinary Elisp definitions, and keep each companion lexically bound.
|
Keep necessary business functions and variables in a same-basename `.el` file beside the `.ebox` layout. The generic runner loads that exact companion source, when present, before every preview evaluation, including `C-c C-c` and `ebox-playground-open-file`. Keep the `.ebox` focused on layout and its data references; do not add helpers merely to hide repeated property lists. Reload state according to ordinary Elisp definitions, and keep each companion lexically bound.
|
||||||
|
|
||||||
For native help, pointer, hover-style, or keymap demonstration work, maintain `examples/interaction-reference.el` and `examples/interaction-reference.ebox`, then run `make interaction-tests` (with `EMACS` set to the intended executable). Extend `tests/ebox-playground-interaction-tests.el` for capability, command, replacement, and removal regressions. Use `ebox-help-create` and `ebox-keymap-create` for zero-argument business callbacks and native help/command buffer context, and use `ebox-region-update` with semantic IDs in that buffer. These reusable adapters belong to Ebox; moving local adapters into the companion is not a substitute. Raw native callbacks and keymaps remain supported and own their event-context handling. Extend the generic runner tests for companion reload behavior rather than adding an example-specific loading mechanism.
|
For native help, pointer, hover-style, or keymap demonstration work, maintain `examples/interaction-reference.el` and `examples/interaction-reference.ebox`, then run `make interaction-tests` (with `EMACS` set to the intended executable). Extend `tests/ebox-playground-interaction-tests.el` for capability, command, replacement, and removal regressions. Use `ebox-help-create` and `ebox-keymap-create` for zero-argument business callbacks and native help/command buffer context, and use `ebox-region-update` with semantic IDs in that buffer. These reusable adapters belong to Ebox; moving local adapters into the companion is not a substitute. Raw native callbacks and keymaps remain supported and own their event-context handling. Extend the generic runner tests for companion reload behavior rather than adding an example-specific loading mechanism.
|
||||||
|
|
||||||
|
For layer, overlap, positioning, or anchored-menu demonstrations, maintain `examples/layer-reference.ebox` and its same-basename companion, preserve `examples/layer-minimal.ebox` as the minimal fixture, and run `make layer-tests`. Extend `tests/ebox-playground-layer-tests.el` for actual native commands and committed-snapshot render parity instead of creating another demo runner or probe. Keep layout in the DSL, business state/actions in the companion, and one font size throughout positioned content. Use the shared interaction evaluator above for repeated layer-command timing. Each parent creates its local stacking group automatically; use `:layer root` on a nested absolute node when root projection is required, with semantic `:anchor` and `:placement` declarations for anchored menus.
|
||||||
|
|||||||
11
Makefile
11
Makefile
@ -7,7 +7,7 @@ EBOX_NATIVE_RELEASE_DIR = $(shell $(EMACS) -Q --batch $(LOAD_PATH) \
|
|||||||
-l ../ebox/ebox-native-reflow.el \
|
-l ../ebox/ebox-native-reflow.el \
|
||||||
--eval '(princ (expand-file-name (format "../ebox/native/target/%s/release/" (ebox-native-reflow--rust-target))))')
|
--eval '(princ (expand-file-name (format "../ebox/native/target/%s/release/" (ebox-native-reflow--rust-target))))')
|
||||||
|
|
||||||
.PHONY: all compile test size-tests interaction-tests check checkdoc load performance performance-prepare \
|
.PHONY: all compile test size-tests interaction-tests layer-tests check checkdoc load performance performance-prepare \
|
||||||
performance-evaluator clean
|
performance-evaluator clean
|
||||||
|
|
||||||
all: check
|
all: check
|
||||||
@ -25,7 +25,14 @@ test: compile
|
|||||||
$(EMACS) -Q --batch $(LOAD_PATH) \
|
$(EMACS) -Q --batch $(LOAD_PATH) \
|
||||||
--eval '(setq load-prefer-newer t native-comp-jit-compilation nil)' \
|
--eval '(setq load-prefer-newer t native-comp-jit-compilation nil)' \
|
||||||
-l tests/ebox-playground-tests.el -l tests/ebox-playground-size-tests.el \
|
-l tests/ebox-playground-tests.el -l tests/ebox-playground-size-tests.el \
|
||||||
-l tests/ebox-playground-interaction-tests.el -f ert-run-tests-batch-and-exit
|
-l tests/ebox-playground-interaction-tests.el -l tests/ebox-playground-layer-tests.el \
|
||||||
|
-f ert-run-tests-batch-and-exit
|
||||||
|
|
||||||
|
layer-tests: compile
|
||||||
|
$(MAKE) -C ../ebox layer-tests EMACS="$(EMACS)" ECSS_DIR="$(ECSS_DIR)" TP_DIR="$(TP_DIR)"
|
||||||
|
$(EMACS) -Q --batch $(LOAD_PATH) \
|
||||||
|
--eval '(setq load-prefer-newer t native-comp-jit-compilation nil)' \
|
||||||
|
-l tests/ebox-playground-layer-tests.el -f ert-run-tests-batch-and-exit
|
||||||
|
|
||||||
interaction-tests: compile
|
interaction-tests: compile
|
||||||
$(MAKE) -C ../ebox interaction-tests EMACS="$(EMACS)" ECSS_DIR="$(ECSS_DIR)" TP_DIR="$(TP_DIR)"
|
$(MAKE) -C ../ebox interaction-tests EMACS="$(EMACS)" ECSS_DIR="$(ECSS_DIR)" TP_DIR="$(TP_DIR)"
|
||||||
|
|||||||
55
README.md
55
README.md
@ -1,6 +1,6 @@
|
|||||||
# ebox-playground
|
# ebox-playground
|
||||||
|
|
||||||
`ebox-playground` is the independent, generic file runner for Ebox DSL examples. It uses only public Ebox APIs; concrete layouts live in readable `.ebox` fixtures under `examples/`. `flex-reference.ebox`, `grid-reference.ebox`, and `size-reference.ebox` demonstrate layout and sizing with a restrained terracotta, sage, blue, violet, ochre, and teal visual language. `interaction-reference.ebox` demonstrates native Ebox interaction and retained updates.
|
`ebox-playground` is the independent, generic file runner for Ebox DSL examples. It uses only public Ebox APIs; concrete layouts live in readable `.ebox` fixtures under `examples/`. `flex-reference.ebox`, `grid-reference.ebox`, and `size-reference.ebox` demonstrate layout and sizing with a restrained terracotta, sage, blue, violet, ochre, and teal visual language. `interaction-reference.ebox` demonstrates native Ebox interaction and retained updates. `layer-reference.ebox` combines retained overlapping panels with a native anchored menu.
|
||||||
|
|
||||||
ECSS 0.1.0 and TP 1.0.1 are independent packages and may be installed in either order. Install both before Ebox 2.0.1, then install Ebox Playground 0.1.1. `ebox-playground` never reaches into those packages' private APIs.
|
ECSS 0.1.0 and TP 1.0.1 are independent packages and may be installed in either order. Install both before Ebox 2.0.1, then install Ebox Playground 0.1.1. `ebox-playground` never reaches into those packages' private APIs.
|
||||||
|
|
||||||
@ -31,6 +31,59 @@ the core size contracts and the actual example declarations, known arithmetic
|
|||||||
results, painted sizing differences, complete rendering, and retained viewport updates. `make check` also
|
results, painted sizing differences, complete rendering, and retained viewport updates. `make check` also
|
||||||
includes the example's regression tests.
|
includes the example's regression tests.
|
||||||
|
|
||||||
|
## Layer lab
|
||||||
|
|
||||||
|
`examples/layer-minimal.ebox` is the smallest static overlap example. The complete
|
||||||
|
lab uses the same generic runner and a companion for application state:
|
||||||
|
|
||||||
|
```elisp
|
||||||
|
(ebox-playground-open-file
|
||||||
|
(expand-file-name "examples/layer-reference.ebox"
|
||||||
|
ebox-playground-directory))
|
||||||
|
```
|
||||||
|
|
||||||
|
Visit the `.ebox` file and press `C-c C-c` for the source/preview split. Every
|
||||||
|
control supports click, `RET`, and `SPC`, with native help and hover paint.
|
||||||
|
All text uses one font size.
|
||||||
|
|
||||||
|
- **UPDATE COVERED NOTE** changes a note fully covered by Panel A. Update it
|
||||||
|
twice, then **TOGGLE PANEL A** to reveal revision 02. The revealed note has
|
||||||
|
its own native action and live help; covering it never removes its logical
|
||||||
|
data or bindings.
|
||||||
|
- **MOVE PANEL A** changes `:left` and `:top` without adding flow rows.
|
||||||
|
**RAISE / LOWER A** changes `:z-index` between 2 and 4 while Panel B stays at
|
||||||
|
3. Opaque panel content and padding replace the lower layer's visible paint.
|
||||||
|
- The relative marker reserves its original flow row and paints one row lower.
|
||||||
|
Each parent forms a local stacking group automatically; no isolation
|
||||||
|
declaration is needed.
|
||||||
|
- **OPEN / CLOSE ROOT MENU** shows a menu whose logical owner clips to one
|
||||||
|
row. The nested absolute menu declares `:layer root` and anchors to the
|
||||||
|
trigger's semantic ID, so it can paint beyond that local clip.
|
||||||
|
**CYCLE PLACEMENT** visits `bottom-start`, `bottom-end`, `top-start`, and
|
||||||
|
`top-end`; **MOVE ANCHOR** moves the trigger and its open menu together.
|
||||||
|
Choosing sage or amber updates the result and closes the menu.
|
||||||
|
|
||||||
|
`layer-reference.ebox` contains layout data and state references;
|
||||||
|
`layer-reference.el` owns state and business actions. It uses only public
|
||||||
|
`ebox-keymap-create`, `ebox-help-create`, and `ebox-region-update` APIs.
|
||||||
|
Offsets use inline `ch` and block `lh` units. The text renderer composes whole
|
||||||
|
rows, and positioned content must share its host's line height.
|
||||||
|
|
||||||
|
Run the batch checks without opening GUI windows:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make layer-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||||||
|
```
|
||||||
|
|
||||||
|
This target runs core layer contracts and the actual demo's native commands,
|
||||||
|
covered updates, movement, stacking, all four anchor placements, cross-buffer
|
||||||
|
mouse dispatch, and independent previews. After each command, the tests compare
|
||||||
|
the mounted output with a fresh render of the committed snapshot, including
|
||||||
|
paint and native capabilities. `make check` includes the Playground tests.
|
||||||
|
For repeated command timing, use the existing interaction evaluator with
|
||||||
|
`"examples/layer-reference.ebox"` and a toolbar label such as
|
||||||
|
`"UPDATE COVERED NOTE"`; see the invocation below.
|
||||||
|
|
||||||
<a id="native-interaction-lab"></a>
|
<a id="native-interaction-lab"></a>
|
||||||
## Native interaction lab
|
## Native interaction lab
|
||||||
|
|
||||||
|
|||||||
9
examples/layer-minimal.ebox
Normal file
9
examples/layer-minimal.ebox
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
'(box :width (ch 32) :height (lh 5)
|
||||||
|
:background-color "#233044"
|
||||||
|
(box :color "#cbd5e1"
|
||||||
|
"01234567890123456789012345678901\nabcdefghijklmnopqrstuvwxyzABCDEF\n01234567890123456789012345678901\nabcdefghijklmnopqrstuvwxyzABCDEF\n01234567890123456789012345678901")
|
||||||
|
(box :position absolute :left (ch 6) :top (lh 1)
|
||||||
|
:width (ch 18) :height (lh 3) :z-index 1
|
||||||
|
:background-color "#2563eb" :color "white"
|
||||||
|
:padding-inline (ch 1)
|
||||||
|
"Upper layer\nCovers the base\nBase stays alive"))
|
||||||
116
examples/layer-reference.ebox
Normal file
116
examples/layer-reference.ebox
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
;; Layout data only. The matching companion owns state and business actions.
|
||||||
|
(let ((demo (layer-demo-create)))
|
||||||
|
`(column
|
||||||
|
:width (vw 100) :height (vh 100)
|
||||||
|
:padding ((lh 1) (ch 2))
|
||||||
|
:background-color "#F4F3ED" :color "#273B42"
|
||||||
|
(box :padding ((lh 1) (ch 2)) :background-color "#153F4A" :color "#FFFFFF"
|
||||||
|
"EBOX / LAYER LAB\nRetained content, opaque overlap, native interaction.")
|
||||||
|
(box :padding-block (lh 1)
|
||||||
|
"Click a control, or move point into it and press RET / SPC.\n"
|
||||||
|
"All text shares one font size. Scroll with the wheel or C-v when needed.")
|
||||||
|
|
||||||
|
(box "01 / LAYERED DESK absolute position + local stacking")
|
||||||
|
(flex :flex-wrap wrap :gap ((lh 1) (ch 1)) :padding-block (lh 1)
|
||||||
|
:pointer hand :hover-style (:background-color "#CCE0D6")
|
||||||
|
(box :padding-inline (ch 1) :background-color "#DDE9DF"
|
||||||
|
:help-echo "Hide or show Panel A. Revealing a lower layer shows its latest content."
|
||||||
|
:keymap ,(layer-demo-action demo 'toggle) "TOGGLE PANEL A")
|
||||||
|
(box :padding-inline (ch 1) :background-color "#DDE9DF"
|
||||||
|
:help-echo "Move Panel A between left 4ch / top 2lh and left 10ch / top 0lh."
|
||||||
|
:keymap ,(layer-demo-action demo 'move) "MOVE PANEL A")
|
||||||
|
(box :padding-inline (ch 1) :background-color "#DDE9DF"
|
||||||
|
:help-echo "Switch Panel A between z=2 and z=4; Panel B stays at z=3."
|
||||||
|
:keymap ,(layer-demo-action demo 'raise) "RAISE / LOWER A")
|
||||||
|
(box :padding-inline (ch 1) :background-color "#DDE9DF"
|
||||||
|
:help-echo "Update the lower note even when every part of it is covered."
|
||||||
|
:keymap ,(layer-demo-action demo 'update-lower) "UPDATE COVERED NOTE"))
|
||||||
|
(box :id "layer-desk" :height (lh 10) :background-color "#DFE5E4"
|
||||||
|
(box :height (lh 10)
|
||||||
|
"DESK / ordinary content stays in its flow slot\n"
|
||||||
|
"........................................................................\n"
|
||||||
|
"........................................................................\n"
|
||||||
|
"........................................................................\n"
|
||||||
|
"........................................................................\n"
|
||||||
|
"........................................................................\n"
|
||||||
|
"........................................................................\n"
|
||||||
|
"........................................................................\n"
|
||||||
|
"........................................................................\n"
|
||||||
|
"DESK END / panels do not add flow rows")
|
||||||
|
(box :id "lower-note" :position absolute :left (ch 4) :top (lh 2)
|
||||||
|
:width (ch 38) :height (lh 5) :z-index 0 :padding-inline (ch 1)
|
||||||
|
:background-color "#C9DED0" :color "#244F36" :pointer hand
|
||||||
|
:hover-style (:background-color "#ADCAB7")
|
||||||
|
:help-echo ,(ebox-help-create
|
||||||
|
(apply-partially #'layer-demo-help demo 'lower))
|
||||||
|
:keymap ,(layer-demo-action demo 'lower)
|
||||||
|
,(layer-demo-lower-content demo))
|
||||||
|
(box :id "panel-a" :position absolute :left (ch 4) :top (lh 2)
|
||||||
|
:width (ch 38) :height (lh 5) :z-index 2 :padding-inline (ch 1)
|
||||||
|
:background-color "#3A687E" :color "#FFFFFF"
|
||||||
|
:help-echo "Panel A is opaque, including its blank padding. Use the toolbar to move or raise it."
|
||||||
|
"PANEL A / blue\nMove and raise from the toolbar.\nA survives under the amber panel.\nOpaque padding covers too.\nThe covered note remains alive.")
|
||||||
|
(box :id "panel-b" :position absolute :left (ch 22) :top (lh 4)
|
||||||
|
:width (ch 28) :height (lh 4) :z-index 3 :padding-inline (ch 1)
|
||||||
|
:background-color "#D7AF6D" :color "#503B21"
|
||||||
|
:help-echo "Panel B stays at z=3. Raising A changes which panel owns the overlap."
|
||||||
|
"PANEL B / amber\nFixed at z=3.\nLater layers cover earlier\npaint, not logical data."))
|
||||||
|
(box :height (lh 1))
|
||||||
|
|
||||||
|
(box "02 / RELATIVE POSITION reserve the slot, shift the paint")
|
||||||
|
(column :height (lh 4) :background-color "#E8E1EE"
|
||||||
|
(box :id "relative-marker" :position relative :left (ch 4) :top (lh 1)
|
||||||
|
:width (ch 24) :height (lh 1) :z-index 1
|
||||||
|
:background-color "#765D87" :color "#FFFFFF" "RELATIVE / +4ch, +1lh")
|
||||||
|
(box "flow row 2: the shifted marker covers part of this row")
|
||||||
|
(box :id "relative-following" "FLOW ROW 3 / the original slot was kept")
|
||||||
|
(box "Each parent keeps a local stacking group automatically."))
|
||||||
|
(box :height (lh 1))
|
||||||
|
|
||||||
|
(box "03 / ANCHORED MENU nested owner + root projection")
|
||||||
|
(box :padding-block (lh 1)
|
||||||
|
"The owner below clips to one row. Its menu stays a logical child\n"
|
||||||
|
"but paints in the root layer, outside that local clip.")
|
||||||
|
(flex :gap ((lh 0) (ch 1)) :padding-bottom (lh 1) :pointer hand
|
||||||
|
:hover-style (:background-color "#CCE0D6")
|
||||||
|
(box :padding-inline (ch 1) :background-color "#DDE9DF"
|
||||||
|
:help-echo "Cycle bottom-start, bottom-end, top-start, and top-end. Open the menu to compare."
|
||||||
|
:keymap ,(layer-demo-action demo 'placement) "CYCLE PLACEMENT")
|
||||||
|
(box :padding-inline (ch 1) :background-color "#DDE9DF"
|
||||||
|
:help-echo "Move the relative trigger from left 4ch to 12ch. The open menu follows its anchor."
|
||||||
|
:keymap ,(layer-demo-action demo 'anchor) "MOVE ANCHOR"))
|
||||||
|
(box :height (lh 3) :background-color "#E5EBE7"
|
||||||
|
"SPACE ABOVE / top placements can cover this area\n"
|
||||||
|
"........................................................................\n"
|
||||||
|
"........................................................................")
|
||||||
|
(box :id "menu-owner" :height (lh 1) :overflow hidden :background-color "#CFDCD5"
|
||||||
|
(box :id "menu-trigger" :position relative :left (ch 4) :top (lh 0)
|
||||||
|
:width (ch 32) :height (lh 1) :padding-inline (ch 1)
|
||||||
|
:background-color "#286347" :color "#FFFFFF" :pointer hand
|
||||||
|
:hover-style (:background-color "#407E5C")
|
||||||
|
:help-echo ,(ebox-help-create
|
||||||
|
(apply-partially #'layer-demo-help demo 'menu))
|
||||||
|
:keymap ,(layer-demo-action demo 'menu) "OPEN / CLOSE ROOT MENU")
|
||||||
|
(box :id "root-menu" :position absolute :layer root :anchor "menu-trigger"
|
||||||
|
:placement bottom-start :z-index 50 :visibility hidden
|
||||||
|
:width (ch 26) :height (lh 3) :background-color "#153F4A" :color "#FFFFFF"
|
||||||
|
(box :padding-inline (ch 1) "ROOT MENU / choose a color")
|
||||||
|
(box :id "menu-sage" :padding-inline (ch 1) :pointer hand
|
||||||
|
:hover-style (:background-color "#407E5C")
|
||||||
|
:help-echo "Choose sage, update the result, and close the menu."
|
||||||
|
:keymap ,(layer-demo-action demo 'sage) "CHOOSE SAGE")
|
||||||
|
(box :id "menu-amber" :padding-inline (ch 1) :pointer hand
|
||||||
|
:hover-style (:background-color "#895314")
|
||||||
|
:help-echo "Choose amber, update the result, and close the menu."
|
||||||
|
:keymap ,(layer-demo-action demo 'amber) "CHOOSE AMBER")))
|
||||||
|
(box :height (lh 3) :background-color "#E5EBE7"
|
||||||
|
"SPACE BELOW / bottom placements can cover this area\n"
|
||||||
|
"........................................................................\n"
|
||||||
|
"........................................................................")
|
||||||
|
(box :id "menu-result" :padding-block (lh 1) "No menu selection yet.")
|
||||||
|
(box :id "layer-status" :background-color "#153F4A" :color "#FFFFFF"
|
||||||
|
:padding-inline (ch 1) ,(layer-demo-status demo))
|
||||||
|
(box :padding-top (lh 1)
|
||||||
|
"Try: update the covered note twice, hide A, then activate the revealed note.\n"
|
||||||
|
"Open the menu, cycle all four placements, move its anchor, and select a color.\n"
|
||||||
|
"Commands publish named-region updates. Layers do not create a focus system.")))
|
||||||
98
examples/layer-reference.el
Normal file
98
examples/layer-reference.el
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
;;; layer-reference.el --- State and actions for the layer lab -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; The matching .ebox file owns every layout declaration. Each evaluation
|
||||||
|
;; creates independent application state; public Ebox adapters dispatch native
|
||||||
|
;; commands and help in the preview buffer and update semantic region IDs.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'cl-lib)
|
||||||
|
(require 'ebox)
|
||||||
|
|
||||||
|
(cl-defstruct (layer-demo (:constructor layer-demo-create))
|
||||||
|
"Application state belonging to one layer-lab preview."
|
||||||
|
(visible t) moved raised (revision 0) (lower-clicks 0)
|
||||||
|
menu-visible (placement 0) anchor-moved (selection "none"))
|
||||||
|
|
||||||
|
(defconst layer-demo-placements
|
||||||
|
[bottom-start bottom-end top-start top-end]
|
||||||
|
"Anchor placements visited by the lab's placement control.")
|
||||||
|
|
||||||
|
(defun layer-demo-lower-content (demo)
|
||||||
|
"Return the covered note's current content for DEMO."
|
||||||
|
(format "LOWER REV %02d\nUpdated even while covered.\nReveal me, then click this surface.\nLower activations: %02d\nThe logical note never left the tree."
|
||||||
|
(layer-demo-revision demo) (layer-demo-lower-clicks demo)))
|
||||||
|
|
||||||
|
(defun layer-demo-status (demo)
|
||||||
|
"Describe DEMO's currently published panel and menu state."
|
||||||
|
(format "A: %s / %s / z=%d | lower rev=%02d | menu: %s / %s | choice: %s"
|
||||||
|
(if (layer-demo-visible demo) "shown" "hidden")
|
||||||
|
(if (layer-demo-moved demo) "moved" "home")
|
||||||
|
(if (layer-demo-raised demo) 4 2)
|
||||||
|
(layer-demo-revision demo)
|
||||||
|
(if (layer-demo-menu-visible demo) "open" "closed")
|
||||||
|
(aref layer-demo-placements (layer-demo-placement demo))
|
||||||
|
(layer-demo-selection demo)))
|
||||||
|
|
||||||
|
(defun layer-demo-help (demo subject)
|
||||||
|
"Return live native help for DEMO's SUBJECT."
|
||||||
|
(pcase subject
|
||||||
|
('lower (format "Lower note revision %d; activated %d times. RET / SPC / click."
|
||||||
|
(layer-demo-revision demo) (layer-demo-lower-clicks demo)))
|
||||||
|
('menu (format "Menu is %s; placement %s; selected %s. RET / SPC / click."
|
||||||
|
(if (layer-demo-menu-visible demo) "open" "closed")
|
||||||
|
(aref layer-demo-placements (layer-demo-placement demo))
|
||||||
|
(layer-demo-selection demo)))
|
||||||
|
(_ (layer-demo-status demo))))
|
||||||
|
|
||||||
|
(defun layer-demo-action (demo action)
|
||||||
|
"Create a native keymap for DEMO's business ACTION."
|
||||||
|
(ebox-keymap-create
|
||||||
|
:activate
|
||||||
|
(lambda ()
|
||||||
|
(pcase action
|
||||||
|
('toggle
|
||||||
|
(setf (layer-demo-visible demo) (not (layer-demo-visible demo)))
|
||||||
|
(ebox-region-update "panel-a" :visibility
|
||||||
|
(if (layer-demo-visible demo) 'visible 'hidden)))
|
||||||
|
('move
|
||||||
|
(setf (layer-demo-moved demo) (not (layer-demo-moved demo)))
|
||||||
|
(ebox-region-update "panel-a"
|
||||||
|
:left (if (layer-demo-moved demo) '(ch 10) '(ch 4))
|
||||||
|
:top (if (layer-demo-moved demo) '(lh 0) '(lh 2))))
|
||||||
|
('raise
|
||||||
|
(setf (layer-demo-raised demo) (not (layer-demo-raised demo)))
|
||||||
|
(ebox-region-update "panel-a" :z-index
|
||||||
|
(if (layer-demo-raised demo) 4 2)))
|
||||||
|
('update-lower
|
||||||
|
(cl-incf (layer-demo-revision demo))
|
||||||
|
(ebox-region-update "lower-note" :content (layer-demo-lower-content demo)))
|
||||||
|
('lower
|
||||||
|
(cl-incf (layer-demo-lower-clicks demo))
|
||||||
|
(ebox-region-update "lower-note" :content (layer-demo-lower-content demo)))
|
||||||
|
('menu
|
||||||
|
(setf (layer-demo-menu-visible demo) (not (layer-demo-menu-visible demo)))
|
||||||
|
(ebox-region-update "root-menu" :visibility
|
||||||
|
(if (layer-demo-menu-visible demo) 'visible 'hidden)))
|
||||||
|
('placement
|
||||||
|
(setf (layer-demo-placement demo) (% (1+ (layer-demo-placement demo)) 4))
|
||||||
|
(ebox-region-update "root-menu" :placement
|
||||||
|
(aref layer-demo-placements (layer-demo-placement demo))))
|
||||||
|
('anchor
|
||||||
|
(setf (layer-demo-anchor-moved demo) (not (layer-demo-anchor-moved demo)))
|
||||||
|
(ebox-region-update "menu-trigger" :left
|
||||||
|
(if (layer-demo-anchor-moved demo) '(ch 12) '(ch 4))))
|
||||||
|
((or 'sage 'amber)
|
||||||
|
(setf (layer-demo-selection demo) (symbol-name action)
|
||||||
|
(layer-demo-menu-visible demo) nil)
|
||||||
|
(ebox-region-update "menu-result" :content
|
||||||
|
(format "Selected %s from the anchored menu." action)
|
||||||
|
:color (if (eq action 'sage) "#286347" "#895314"))
|
||||||
|
(ebox-region-update "root-menu" :visibility 'hidden))
|
||||||
|
(_ (error "Unknown layer-lab action: %S" action)))
|
||||||
|
(ebox-region-update "layer-status" :content (layer-demo-status demo)))))
|
||||||
|
|
||||||
|
(provide 'layer-demo-reference)
|
||||||
|
|
||||||
|
;;; layer-reference.el ends here
|
||||||
253
tests/ebox-playground-layer-tests.el
Normal file
253
tests/ebox-playground-layer-tests.el
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
;;; ebox-playground-layer-tests.el --- Layer lab command regressions -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Exercise the actual companion and DSL through the generic file runner.
|
||||||
|
;; Commands come from rendered native keymaps. Every publication is compared
|
||||||
|
;; with an independent render of the committed logical input, including paint
|
||||||
|
;; and native interaction properties after normalizing runtime identities.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'ert)
|
||||||
|
(require 'cl-lib)
|
||||||
|
(require 'ebox-playground)
|
||||||
|
(require 'ebox-playground-flex-resize-evaluator
|
||||||
|
(expand-file-name "scripts/ebox-playground-flex-resize-evaluator.el"
|
||||||
|
ebox-playground-directory))
|
||||||
|
|
||||||
|
(defconst ebox-playground-layer-test--file
|
||||||
|
(expand-file-name "examples/layer-reference.ebox" ebox-playground-directory)
|
||||||
|
"Layer lab loaded by this suite through the ordinary file runner.")
|
||||||
|
|
||||||
|
(cl-defmacro ebox-playground-layer-test--with-preview
|
||||||
|
((buffer &optional (width 720)) &rest body)
|
||||||
|
"Run BODY in a fresh BUFFER preview of WIDTH pixels and clean up afterward."
|
||||||
|
(declare (indent 1) (debug ((symbolp &optional form) body)))
|
||||||
|
`(let ((,buffer (generate-new-buffer " *ebox-layer-test*"))
|
||||||
|
(ebox-viewport-width ,width) (ebox-viewport-height 100)
|
||||||
|
(case-fold-search nil)
|
||||||
|
(ebox-runtime-idle-prewarm nil)
|
||||||
|
(ebox-runtime-idle-reflow-cache-prewarm nil))
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(ebox-playground-open-file
|
||||||
|
ebox-playground-layer-test--file (buffer-name ,buffer))
|
||||||
|
(with-current-buffer ,buffer ,@body))
|
||||||
|
(when (buffer-live-p ,buffer)
|
||||||
|
(ebox-playground-close (buffer-name ,buffer))))))
|
||||||
|
|
||||||
|
(defun ebox-playground-layer-test--position (label)
|
||||||
|
"Return the first visible position of LABEL in the current preview."
|
||||||
|
(save-excursion
|
||||||
|
(goto-char (point-min))
|
||||||
|
(unless (search-forward label nil t)
|
||||||
|
(ert-fail (format "Missing visible layer label: %s" label)))
|
||||||
|
(- (point) (length label))))
|
||||||
|
|
||||||
|
(defun ebox-playground-layer-test--coordinates (label)
|
||||||
|
"Return LABEL's visible horizontal pixel and zero-based row coordinates."
|
||||||
|
(save-excursion
|
||||||
|
(goto-char (ebox-playground-layer-test--position label))
|
||||||
|
(cons (ebox-string-pixel-width
|
||||||
|
(buffer-substring (line-beginning-position) (point)))
|
||||||
|
(1- (line-number-at-pos)))))
|
||||||
|
|
||||||
|
(defun ebox-playground-layer-test--parity ()
|
||||||
|
"Compare native paint and interactions with the committed snapshot render."
|
||||||
|
(let* ((snapshot (ebox-surface-buffer-snapshot (current-buffer)))
|
||||||
|
(fresh (ebox-render (plist-get snapshot :input))))
|
||||||
|
(should (ebox-canonical-input-p (plist-get snapshot :input)))
|
||||||
|
(should
|
||||||
|
(equal-including-properties
|
||||||
|
(ebox-playground-scroll-evaluator--visual-text (buffer-string))
|
||||||
|
(ebox-playground-scroll-evaluator--visual-text fresh)))))
|
||||||
|
|
||||||
|
(defun ebox-playground-layer-test--activate (label &optional key)
|
||||||
|
"Invoke the native command at LABEL using KEY, then verify its publication."
|
||||||
|
(let* ((keys (or key (kbd "RET")))
|
||||||
|
(position (ebox-playground-layer-test--position label))
|
||||||
|
(map (get-text-property position 'keymap)))
|
||||||
|
(goto-char position)
|
||||||
|
(should (keymapp map))
|
||||||
|
(let ((command (key-binding keys))
|
||||||
|
(last-input-event (aref (vconcat keys) (1- (length keys)))))
|
||||||
|
(should (commandp command))
|
||||||
|
(should (eq command (lookup-key map keys)))
|
||||||
|
(call-interactively command)
|
||||||
|
(ebox-playground-layer-test--parity)
|
||||||
|
command)))
|
||||||
|
|
||||||
|
(defun ebox-playground-layer-test--click (label)
|
||||||
|
"Dispatch LABEL's native mouse command while another buffer is current."
|
||||||
|
(let* ((buffer (current-buffer))
|
||||||
|
(position (ebox-playground-layer-test--position label))
|
||||||
|
(map (get-text-property position 'keymap)))
|
||||||
|
(save-window-excursion
|
||||||
|
(set-window-buffer (selected-window) buffer)
|
||||||
|
(let* ((event (list 'mouse-1 (list (selected-window) position '(0 . 0) 0)))
|
||||||
|
(last-input-event event)
|
||||||
|
(command (key-binding [mouse-1] nil nil position)))
|
||||||
|
(should (commandp command))
|
||||||
|
(should (eq command (lookup-key map [mouse-1])))
|
||||||
|
(with-temp-buffer
|
||||||
|
(call-interactively command nil (vector event))
|
||||||
|
(should (zerop (buffer-size))))))
|
||||||
|
(ebox-playground-layer-test--parity)))
|
||||||
|
|
||||||
|
(defun ebox-playground-layer-test--nodes (form)
|
||||||
|
"Return DSL nodes in FORM without walking opaque property values."
|
||||||
|
(when (and (consp form) (memq (car form) '(box text row column flex grid)))
|
||||||
|
(let ((tail (cdr form)) (nodes (list form)))
|
||||||
|
(while tail
|
||||||
|
(if (keywordp (car tail))
|
||||||
|
(setq tail (cddr tail))
|
||||||
|
(setq nodes (append nodes (ebox-playground-layer-test--nodes (car tail)))
|
||||||
|
tail (cdr tail))))
|
||||||
|
nodes)))
|
||||||
|
|
||||||
|
(ert-deftest ebox-playground-layer-reference-declares-layers-with-one-font-size ()
|
||||||
|
"Actual layout data contains the taught contracts and no font-size override."
|
||||||
|
(let* ((form (ebox-playground--evaluate-form
|
||||||
|
(ebox-playground--read-file ebox-playground-layer-test--file)
|
||||||
|
ebox-playground-layer-test--file))
|
||||||
|
(nodes (ebox-playground-layer-test--nodes form))
|
||||||
|
(owner (cl-find-if
|
||||||
|
(lambda (node) (equal (plist-get (cdr node) :id) "menu-owner"))
|
||||||
|
nodes))
|
||||||
|
(menu (cl-find-if
|
||||||
|
(lambda (node) (equal (plist-get (cdr node) :id) "root-menu"))
|
||||||
|
(ebox-playground-layer-test--nodes owner))))
|
||||||
|
(should (ebox-canonical-input-p (ebox-build form)))
|
||||||
|
(should (eq (plist-get (cdr owner) :overflow) 'hidden))
|
||||||
|
(should (equal (plist-get (cdr owner) :height) '(lh 1)))
|
||||||
|
(should (eq (plist-get (cdr menu) :layer) 'root))
|
||||||
|
(should (equal (plist-get (cdr menu) :anchor) "menu-trigger"))
|
||||||
|
(dolist (position '(relative absolute))
|
||||||
|
(should (cl-some (lambda (node)
|
||||||
|
(eq (plist-get (cdr node) :position) position)) nodes)))
|
||||||
|
(dolist (node nodes)
|
||||||
|
(should-not (plist-member (cdr node) :isolation))
|
||||||
|
(should-not (plist-member (cdr node) :font-size)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-playground-layer-covered-updates-reveal-latest-native-surface ()
|
||||||
|
"Covered text remains retained; revealing it restores current native help."
|
||||||
|
(ebox-playground-layer-test--with-preview (buffer)
|
||||||
|
(should-not (string-match-p "LOWER REV" (buffer-string)))
|
||||||
|
(should (ebox-region-resolve buffer "lower-note"))
|
||||||
|
(ebox-playground-layer-test--activate "UPDATE COVERED NOTE")
|
||||||
|
(ebox-playground-layer-test--activate "UPDATE COVERED NOTE" (kbd "SPC"))
|
||||||
|
(should-not (string-match-p "LOWER REV" (buffer-string)))
|
||||||
|
(should (string-match-p "lower rev=02" (buffer-string)))
|
||||||
|
(ebox-playground-layer-test--activate "TOGGLE PANEL A")
|
||||||
|
(let* ((position (ebox-playground-layer-test--position "LOWER REV 02"))
|
||||||
|
(help (get-text-property position 'help-echo)))
|
||||||
|
(should (eq (get-text-property position 'pointer) 'hand))
|
||||||
|
(should (get-text-property position 'mouse-face))
|
||||||
|
(should (functionp help))
|
||||||
|
(should (string-match-p "revision 2; activated 0"
|
||||||
|
(funcall help (selected-window) buffer position))))
|
||||||
|
(ebox-playground-layer-test--activate "LOWER REV 02" [return])
|
||||||
|
(let* ((position (ebox-playground-layer-test--position "LOWER REV 02"))
|
||||||
|
(help (get-text-property position 'help-echo)))
|
||||||
|
(should (string-match-p "revision 2; activated 1"
|
||||||
|
(funcall help (selected-window) buffer position))))
|
||||||
|
(ebox-playground-layer-test--activate "TOGGLE PANEL A")
|
||||||
|
(should-not (string-match-p "LOWER REV" (buffer-string)))
|
||||||
|
(ebox-playground-layer-test--activate "TOGGLE PANEL A")
|
||||||
|
(should (integerp (ebox-playground-layer-test--position "LOWER REV 02")))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-playground-layer-move-and-raise-preserve-flow-and-coverage ()
|
||||||
|
"Moving changes paint coordinates; raising changes only overlap ownership."
|
||||||
|
(ebox-playground-layer-test--with-preview (buffer)
|
||||||
|
(let ((home (ebox-playground-layer-test--coordinates "PANEL A / blue"))
|
||||||
|
(desk-end (ebox-playground-layer-test--coordinates "DESK END")))
|
||||||
|
(should-not (string-match-p "A survives under the amber panel" (buffer-string)))
|
||||||
|
(ebox-playground-layer-test--activate "RAISE / LOWER A")
|
||||||
|
(should (integerp (ebox-playground-layer-test--position
|
||||||
|
"A survives under the amber panel")))
|
||||||
|
(should-not (string-match-p "PANEL B / amber" (buffer-string)))
|
||||||
|
(should (equal home (ebox-playground-layer-test--coordinates "PANEL A / blue")))
|
||||||
|
(ebox-playground-layer-test--activate "RAISE / LOWER A")
|
||||||
|
(should (integerp (ebox-playground-layer-test--position "PANEL B / amber")))
|
||||||
|
(ebox-playground-layer-test--activate "MOVE PANEL A")
|
||||||
|
(let ((moved (ebox-playground-layer-test--coordinates "PANEL A / blue")))
|
||||||
|
(should (> (car moved) (car home)))
|
||||||
|
(should (= (cdr moved) (- (cdr home) 2))))
|
||||||
|
(should (equal desk-end (ebox-playground-layer-test--coordinates "DESK END")))
|
||||||
|
(ebox-playground-layer-test--activate "MOVE PANEL A")
|
||||||
|
(should (equal home (ebox-playground-layer-test--coordinates "PANEL A / blue"))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-playground-layer-relative-marker-reserves-its-original-row ()
|
||||||
|
"The relative marker moves into row two while row three retains its slot."
|
||||||
|
(ebox-playground-layer-test--with-preview (buffer)
|
||||||
|
(let ((marker (ebox-playground-layer-test--coordinates "RELATIVE / +4ch, +1lh"))
|
||||||
|
(following (ebox-playground-layer-test--coordinates "FLOW ROW 3")))
|
||||||
|
(should (= (1+ (cdr marker)) (cdr following)))
|
||||||
|
(should (> (car marker) (car following)))
|
||||||
|
(save-excursion
|
||||||
|
(goto-char (ebox-playground-layer-test--position "RELATIVE / +4ch, +1lh"))
|
||||||
|
(forward-line -1)
|
||||||
|
(should (string-match-p
|
||||||
|
"\\`[[:space:]]*\\'"
|
||||||
|
(buffer-substring-no-properties
|
||||||
|
(line-beginning-position) (line-end-position)))))
|
||||||
|
(ebox-playground-layer-test--parity))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-playground-layer-root-menu-follows-all-four-anchor-placements ()
|
||||||
|
"The nested menu escapes its one-row owner and follows the moved trigger."
|
||||||
|
(ebox-playground-layer-test--with-preview (buffer)
|
||||||
|
(should-not (string-match-p "ROOT MENU / choose" (buffer-string)))
|
||||||
|
(ebox-playground-layer-test--activate "OPEN / CLOSE ROOT MENU")
|
||||||
|
(let* ((trigger (ebox-playground-layer-test--coordinates "OPEN / CLOSE ROOT MENU"))
|
||||||
|
(bottom-start (ebox-playground-layer-test--coordinates "ROOT MENU / choose")))
|
||||||
|
(should (= (cdr bottom-start) (1+ (cdr trigger))))
|
||||||
|
(should (= (car bottom-start) (car trigger)))
|
||||||
|
(ebox-playground-layer-test--activate "CYCLE PLACEMENT")
|
||||||
|
(let ((bottom-end (ebox-playground-layer-test--coordinates "ROOT MENU / choose")))
|
||||||
|
(should (= (cdr bottom-end) (cdr bottom-start)))
|
||||||
|
(should (> (car bottom-end) (car bottom-start)))
|
||||||
|
(ebox-playground-layer-test--activate "CYCLE PLACEMENT")
|
||||||
|
(let ((top-start (ebox-playground-layer-test--coordinates "ROOT MENU / choose")))
|
||||||
|
(should (= (cdr top-start) (- (cdr trigger) 3)))
|
||||||
|
(should (= (car top-start) (car bottom-start))))
|
||||||
|
(ebox-playground-layer-test--activate "CYCLE PLACEMENT")
|
||||||
|
(let ((top-end (ebox-playground-layer-test--coordinates "ROOT MENU / choose")))
|
||||||
|
(should (= (cdr top-end) (- (cdr trigger) 3)))
|
||||||
|
(should (= (car top-end) (car bottom-end))))
|
||||||
|
(ebox-playground-layer-test--activate "CYCLE PLACEMENT"))
|
||||||
|
(should (equal bottom-start (ebox-playground-layer-test--coordinates "ROOT MENU / choose")))
|
||||||
|
(ebox-playground-layer-test--activate "MOVE ANCHOR")
|
||||||
|
(let ((moved-trigger (ebox-playground-layer-test--coordinates "OPEN / CLOSE ROOT MENU"))
|
||||||
|
(moved-menu (ebox-playground-layer-test--coordinates "ROOT MENU / choose")))
|
||||||
|
(should (> (car moved-trigger) (car trigger)))
|
||||||
|
(should (= (- (car moved-menu) (car bottom-start))
|
||||||
|
(- (car moved-trigger) (car trigger))))))
|
||||||
|
(let ((position (ebox-playground-layer-test--position "CHOOSE SAGE")))
|
||||||
|
(dolist (property '(keymap mouse-face help-echo pointer))
|
||||||
|
(should (get-text-property position property))))
|
||||||
|
(ebox-playground-layer-test--click "CHOOSE SAGE")
|
||||||
|
(should-not (string-match-p "ROOT MENU / choose" (buffer-string)))
|
||||||
|
(should (integerp (ebox-playground-layer-test--position
|
||||||
|
"Selected sage from the anchored menu.")))
|
||||||
|
(ebox-playground-layer-test--activate "OPEN / CLOSE ROOT MENU")
|
||||||
|
(ebox-playground-layer-test--activate "CHOOSE AMBER" (kbd "SPC"))
|
||||||
|
(should (integerp (ebox-playground-layer-test--position
|
||||||
|
"Selected amber from the anchored menu.")))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-playground-layer-previews-retain-independent-state-at-two-widths ()
|
||||||
|
"Separate previews keep independent notes and menus, with bounded rendering."
|
||||||
|
(ebox-playground-layer-test--with-preview (first)
|
||||||
|
(ebox-playground-layer-test--activate "UPDATE COVERED NOTE")
|
||||||
|
(ebox-playground-layer-test--with-preview (second 1000)
|
||||||
|
(should (string-match-p "lower rev=00" (buffer-string)))
|
||||||
|
(ebox-playground-layer-test--activate "OPEN / CLOSE ROOT MENU")
|
||||||
|
(with-current-buffer first
|
||||||
|
(should (string-match-p "lower rev=01" (buffer-string)))
|
||||||
|
(should-not (string-match-p "ROOT MENU / choose" (buffer-string)))
|
||||||
|
(ebox-playground-layer-test--parity))
|
||||||
|
(dolist (line (ebox-string-lines (buffer-string)))
|
||||||
|
(should (<= (ebox-string-pixel-width line) 1000)))
|
||||||
|
(ebox-playground-layer-test--parity))))
|
||||||
|
|
||||||
|
(provide 'ebox-playground-layer-tests)
|
||||||
|
;;; ebox-playground-layer-tests.el ends here
|
||||||
Loading…
Reference in New Issue
Block a user