Self-contained Mark/Scan/Get mouse hit-testing
Own your own Scanner instance — no global Manager wiring required. Wrap rendered chunks with invisible Unicode-sentinel markers, scan to discover bounding boxes, then hit-test mouse events. ZoneClickTracker deduplicates Press/Release pairs so each logical click emits once.
composer require sugarcraft/candy-mouse
use SugarCraft\Mouse\Mark;
use SugarCraft\Mouse\Scanner;
use SugarCraft\Mouse\ZoneClickTracker;
use SugarCraft\Mouse\MouseEvent;
// 1. Wrap interactive content with invisible zone markers.
$rendered = Mark::zone('btn-ok', ' OK ')
. Mark::zone('btn-cancel', 'Cancel');
// 2. Scan after rendering to populate the zone registry.
$scanner = Scanner::new()->scan($rendered);
// 3. Reverse-lookup on mouse events.
$zone = $scanner->hit($mouseX, $mouseY); // ?Zone
// 4. Deduplicate clicks so each press+release pair emits one click.
$tracker = new ZoneClickTracker();
$result = $tracker->track(new MouseEvent(5, 1, 0, MouseAction::Release));
if ($result !== null) {
echo "Clicked zone: " . $result->zone->id;
}
Mark::zone($id, $content) wraps content in invisible U+E000/U+E001 codepoints safe from ANSI SGR clobbering.Scanner::new()->scan($rendered) parses sentinels; hit(col, row) reverse-lookup by coordinates; get(id) lookup by zone id.Zone readonly value object with 1-based start/end col/row coordinates matching terminal cell space.ZoneClickTracker state machine per button — suppresses drag spurious presses and mismatched Press+Release on different zones.Width::string() from candy-core so wide East-Asian characters account for 2 cell columns each.| Class | Method | Description |
|---|---|---|
| Mark | ::zone(id, content) | Wrap content with invisible sentinel markers |
| Scanner | ::new() | Create empty scanner |
| Scanner | ->scan(rendered) | Parse sentinels, build zone registry |
| Scanner | ->get(id) | Get Zone by id |
| Scanner | ->hit(col, row) | Reverse-lookup Zone at coordinates |
| Zone | $id | Zone identifier |
| Zone | $startCol / $startRow / $endCol / $endRow | 1-based bounding box |
| MouseEvent | ::press(x, y, button) | Factory for press event |
| MouseEvent | ::release(x, y, button) | Factory for release event |
| MouseAction | Press / Release / Drag / Scroll | Mouse action enum |
| ZoneClickTracker | ->track(MouseEvent) | Feed event, receive ClickResult or null |
| ZoneClickTracker | ->setPressZone(Zone) | Set zone for pending press (call after track(Press)) |
| ClickResult | $zone / $button | Completed click result |
VHS-recorded GIFs of every example shipped with the library. Regenerated automatically on every push that touches the source.