docs: migrate README to API-Doc-Convention (S2-cascade.libs)
Restructured per ADR-0038: Abstract + bold-list Badges + Topology H2 + H3-API-Subsections (one per public function) + Conventions/Consumer pattern/CHANGELOG/References. Topology auto-populated via Sporel.exe --lint --fix. Pre-commit hook installed via --install-hooks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
13
.githooks/pre-commit
Normal file
13
.githooks/pre-commit
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
# Sporel API-Doc-Convention pre-commit hook.
|
||||
SPOREL_EXE="${SPOREL_EXE:-$(command -v Sporel.exe 2>/dev/null || command -v sporel 2>/dev/null)}"
|
||||
if [ -z "$SPOREL_EXE" ]; then
|
||||
echo "INFO: Sporel.exe not on PATH. Skipping lint." >&2
|
||||
exit 0
|
||||
fi
|
||||
"$SPOREL_EXE" --lint="$(pwd)" --fix
|
||||
RC=$?
|
||||
if git diff --cached --name-only | grep -qx 'README.md'; then
|
||||
git add README.md
|
||||
fi
|
||||
exit $RC
|
||||
231
README.md
231
README.md
@@ -1,12 +1,211 @@
|
||||
# lib-core.selection — v0.1.0
|
||||
# lib-core.selection
|
||||
|
||||
Click-Select + Drag-Box-Select + Selection-Set-State.
|
||||
Click-select + drag-box-select + selection-set-state. Handle-registry pattern: modules register AABB-callbacks, the lib polls them during hit-test. State-machine (`idle`/`ambiguous`/`dragging`) disambiguates click-vs-drag via pixel-threshold. Modifier-bindings via action-names (`mod_add`, `mod_toggle`). Render via alpha-tinted draw_rect helpers.
|
||||
|
||||
## Quick Setup
|
||||
**Version:** 0.1.0
|
||||
**Lib-ID:** lib-core.selection
|
||||
**Requires:** lib-core.input v>=0.4.0, lib-core.camera v>=0.3.0
|
||||
**Tags:** selection, rts, click, drag-box, hit-test
|
||||
|
||||
## Topology
|
||||
|
||||
<!-- topology:start (auto-generated; do not edit) -->
|
||||
```mermaid
|
||||
graph LR
|
||||
this["lib-core.selection"]
|
||||
lib_core_input["lib-core.input"]
|
||||
this --> lib_core_input
|
||||
lib_core_camera["lib-core.camera"]
|
||||
this --> lib_core_camera
|
||||
engine["engine.*"]
|
||||
this --> engine
|
||||
```
|
||||
<!-- topology:end -->
|
||||
|
||||
## API
|
||||
|
||||
### `selection.register(aabb_fn)`
|
||||
**Syntax:** `selection.register(aabb_fn: fun() -> {x: number, y: number, w: number, h: number}) -> handle`
|
||||
|
||||
**Example:**
|
||||
```lua
|
||||
local h = selection.register(function()
|
||||
return { x = entity.x, y = entity.y, w = entity.w, h = entity.h }
|
||||
end)
|
||||
```
|
||||
|
||||
**Description:** Registers a selectable entity. Caller supplies an AABB-callback returning current world-space `{x, y, w, h}`. Returns an opaque handle.
|
||||
|
||||
### `selection.unregister(handle)`
|
||||
**Syntax:** `selection.unregister(handle: handle) -> void`
|
||||
|
||||
**Description:** Removes a selectable. Also removes from the active selection-set if present. No-op for unknown handles.
|
||||
|
||||
### `selection.count_registered()`
|
||||
**Syntax:** `selection.count_registered() -> integer`
|
||||
|
||||
**Description:** Returns the number of registered selectables.
|
||||
|
||||
### `selection.list()`
|
||||
**Syntax:** `selection.list() -> handle[]`
|
||||
|
||||
**Description:** Returns an array of currently-selected handles.
|
||||
|
||||
### `selection.count()`
|
||||
**Syntax:** `selection.count() -> integer`
|
||||
|
||||
**Description:** Returns the size of the selection-set.
|
||||
|
||||
### `selection.contains(handle)`
|
||||
**Syntax:** `selection.contains(handle: handle) -> bool`
|
||||
|
||||
**Description:** Returns true iff `handle` is in the selection-set.
|
||||
|
||||
### `selection.is_empty()`
|
||||
**Syntax:** `selection.is_empty() -> bool`
|
||||
|
||||
**Description:** Returns true iff the selection-set is empty.
|
||||
|
||||
### `selection.add(handle)`
|
||||
**Syntax:** `selection.add(handle: handle) -> void`
|
||||
|
||||
**Description:** Adds a handle to the selection-set. No-op if already selected or handle unknown.
|
||||
|
||||
### `selection.remove(handle)`
|
||||
**Syntax:** `selection.remove(handle: handle) -> void`
|
||||
|
||||
**Description:** Removes a handle from the selection-set. No-op if not selected.
|
||||
|
||||
### `selection.toggle(handle)`
|
||||
**Syntax:** `selection.toggle(handle: handle) -> void`
|
||||
|
||||
**Description:** Adds the handle if absent, removes if present.
|
||||
|
||||
### `selection.clear()`
|
||||
**Syntax:** `selection.clear() -> void`
|
||||
|
||||
**Description:** Empties the selection-set.
|
||||
|
||||
### `selection.bind_action(action_name)`
|
||||
**Syntax:** `selection.bind_action(action_name: string) -> void`
|
||||
|
||||
**Example:**
|
||||
```lua
|
||||
input.bind("lmb", { "mouse_left" })
|
||||
selection.bind_action("lmb")
|
||||
```
|
||||
|
||||
**Description:** Binds the primary select-action (typically LMB). Press starts a potential click/drag; release resolves it (click selects the topmost AABB under the cursor; drag selects all AABBs intersecting the box).
|
||||
|
||||
### `selection.bind_modifier_add(action_name)`
|
||||
**Syntax:** `selection.bind_modifier_add(action_name: string) -> void`
|
||||
|
||||
**Description:** Binds a modifier-action (typically Shift). While held, new selections ADD to the existing set instead of replacing.
|
||||
|
||||
### `selection.bind_modifier_toggle(action_name)`
|
||||
**Syntax:** `selection.bind_modifier_toggle(action_name: string) -> void`
|
||||
|
||||
**Description:** Binds a modifier-action (typically Ctrl). While held, selections TOGGLE instead of replacing.
|
||||
|
||||
### `selection.set_drag_threshold(px)`
|
||||
**Syntax:** `selection.set_drag_threshold(px: number) -> void`
|
||||
|
||||
**Description:** Sets the pixel-distance threshold that disambiguates click vs drag. Default ~4 px.
|
||||
|
||||
### `selection.drag_threshold()`
|
||||
**Syntax:** `selection.drag_threshold() -> number`
|
||||
|
||||
**Description:** Returns the current drag-threshold.
|
||||
|
||||
### `selection.set_enabled(b)`
|
||||
**Syntax:** `selection.set_enabled(b: bool) -> void`
|
||||
|
||||
**Description:** Master enable/disable.
|
||||
|
||||
### `selection.enabled()`
|
||||
**Syntax:** `selection.enabled() -> bool`
|
||||
|
||||
**Description:** Returns master-enabled state.
|
||||
|
||||
### `selection.drag_state()`
|
||||
**Syntax:** `selection.drag_state() -> string`
|
||||
|
||||
**Description:** Returns `"idle"`, `"ambiguous"` (button down but haven't crossed drag-threshold yet), or `"dragging"`.
|
||||
|
||||
### `selection.box_rect()`
|
||||
**Syntax:** `selection.box_rect() -> {x: number, y: number, w: number, h: number} | nil`
|
||||
|
||||
**Description:** Returns the current drag-box in screen-space, or `nil` if not currently dragging.
|
||||
|
||||
### `selection.set_drag_box_color(r, g, b, a)`
|
||||
**Syntax:** `selection.set_drag_box_color(r: integer, g: integer, b: integer, a: integer) -> void`
|
||||
|
||||
**Description:** Sets the alpha-tinted fill color of the drag-box overlay.
|
||||
|
||||
### `selection.drag_box_color()`
|
||||
**Syntax:** `selection.drag_box_color() -> integer, integer, integer, integer`
|
||||
|
||||
**Description:** Returns current drag-box color components.
|
||||
|
||||
### `selection.set_highlight_color(r, g, b, a)`
|
||||
**Syntax:** `selection.set_highlight_color(r: integer, g: integer, b: integer, a: integer) -> void`
|
||||
|
||||
**Description:** Sets the alpha-tinted color used by `render_highlights` for selected entities.
|
||||
|
||||
### `selection.highlight_color()`
|
||||
**Syntax:** `selection.highlight_color() -> integer, integer, integer, integer`
|
||||
|
||||
**Description:** Returns current highlight color components.
|
||||
|
||||
### `selection.update(dt)`
|
||||
**Syntax:** `selection.update(dt: number) -> void`
|
||||
|
||||
**Example:**
|
||||
```lua
|
||||
function update(ctx, dt) selection.update(dt) end
|
||||
```
|
||||
|
||||
**Description:** Per-frame state-machine: tracks press/release, threshold-crossing, drag-box assembly, hit-test on release, applies add/toggle/replace based on modifiers.
|
||||
|
||||
### `selection.render_drag_box()`
|
||||
**Syntax:** `selection.render_drag_box() -> void`
|
||||
|
||||
**Example:**
|
||||
```lua
|
||||
selection.render_drag_box() -- screen-space, draw after camera.finish()
|
||||
```
|
||||
|
||||
**Description:** Draws the alpha-tinted drag-box (screen-space). No-op when not dragging.
|
||||
|
||||
### `selection.render_highlights()`
|
||||
**Syntax:** `selection.render_highlights() -> void`
|
||||
|
||||
**Example:**
|
||||
```lua
|
||||
camera.begin()
|
||||
-- world entities
|
||||
selection.render_highlights() -- world-space
|
||||
camera.finish()
|
||||
```
|
||||
|
||||
**Description:** Draws alpha-tinted highlights over all selected AABBs (world-space). Call inside `camera.begin/finish`.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Handle-registry pattern (parallel to `lib-core.interaction`).
|
||||
- Last-registered = end-of-array; click hit-test picks the last-matching AABB (top-most by registration order).
|
||||
- Drag-box is screen-space; highlights are world-space.
|
||||
- Click vs drag disambiguated by pixel-threshold from press-position.
|
||||
- Modifier semantics: `mod_add` adds to set, `mod_toggle` toggles; no modifier = replace.
|
||||
- v0.1.0 (P.3.10 patch): uses `lib-core.input.was_action_released` (no more `prev_click_down` workaround).
|
||||
- Y-down-positive per ADR-0031.
|
||||
|
||||
## Consumer pattern
|
||||
|
||||
```lua
|
||||
local selection = require("lib-core.selection")
|
||||
local input = require("lib-core.input")
|
||||
local camera = require("lib-core.camera")
|
||||
|
||||
input.bind("lmb", { "mouse_left" })
|
||||
input.bind("mod_add", { "shift" })
|
||||
@@ -20,26 +219,26 @@ local handle = selection.register(function()
|
||||
return { x = entity.x, y = entity.y, w = entity.w, h = entity.h }
|
||||
end)
|
||||
|
||||
-- per-frame:
|
||||
function update(ctx, dt) selection.update(dt) end
|
||||
function update(ctx, dt) selection.update(dt) end
|
||||
|
||||
function render(ctx)
|
||||
camera.begin()
|
||||
-- entities…
|
||||
-- draw entities
|
||||
selection.render_highlights() -- world-space
|
||||
camera.finish()
|
||||
selection.render_drag_box() -- screen-space
|
||||
end
|
||||
```
|
||||
|
||||
## API
|
||||
## CHANGELOG
|
||||
|
||||
- Registry: `register(aabb_fn) → handle`, `unregister(h)`, `count_registered()`
|
||||
- Set-Query: `list()`, `count()`, `contains(h)`, `is_empty()`
|
||||
- Set-Mutation: `add(h)`, `remove(h)`, `toggle(h)`, `clear()`
|
||||
- Bindings: `bind_action(name)`, `bind_modifier_add(name)`, `bind_modifier_toggle(name)`
|
||||
- State-Config: `set_drag_threshold(px)` / `drag_threshold()`, `set_enabled(bool)` / `enabled()`
|
||||
- State-Query: `drag_state() → "idle"|"ambiguous"|"dragging"`, `box_rect() → {x,y,w,h}|nil`
|
||||
- Visual: `set_drag_box_color(r,g,b,a)` / `drag_box_color()`, `set_highlight_color(r,g,b,a)` / `highlight_color()`
|
||||
- Per-Frame: `update(dt)`, `render_drag_box()`, `render_highlights()`
|
||||
### v0.1.0 (P.3.6 + P.3.10 patch)
|
||||
- Initial release: click-select + drag-box-select + selection-set + add/toggle modifiers.
|
||||
- P.3.10 patch: uses `lib-core.input.was_action_released`; `prev_click_down` workaround removed.
|
||||
|
||||
See `meta/docs/superpowers/specs/2026-05-14-p3-6-lib-selection-design.md` for full design.
|
||||
## References
|
||||
|
||||
- Spec v0.1.0 (P.3.6): `meta/docs/superpowers/specs/2026-05-14-p3-6-lib-selection-design.md`
|
||||
- ADR-0001 (engine knows verbs, libs bring nouns)
|
||||
- ADR-0031 (pixel-convention: Y-down-positive)
|
||||
- ADR-0038 (API-Doc-Convention)
|
||||
|
||||
3
scripts/install-hooks.sh
Normal file
3
scripts/install-hooks.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
git config core.hooksPath .githooks
|
||||
echo "Hooks activated."
|
||||
Reference in New Issue
Block a user