sugarcraft/candy-mouse

Self-contained Mark/Scan/Get mouse hit-testing (bubblezone pattern) plus ZoneClickTracker for press/release deduplication. No external Manager wiring needed.

Maintainers

Package info

github.com/sugarcraft/candy-mouse

Documentation

pkg:composer/sugarcraft/candy-mouse

Transparency log

Statistics

Installs: 1 379

Dependents: 5

Suggesters: 0

Stars: 0

Open Issues: 0

dev-master 2026-07-11 13:07 UTC

This package is auto-updated.

Last update: 2026-07-13 00:24:40 UTC


README

Self-contained Mark/Scan/Get mouse hit-testing (bubblezone pattern) plus ZoneClickTracker for press/release deduplication. No external Manager wiring needed.

composer require sugarcraft/candy-mouse: dev-master

Role

Replaces the model where consumers wire candy-zone's Manager externally. Each consumer owns its own Scanner instance — click handling stays local, no shared global state.

Quickstart

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;
}

Key classes

Class Role
Mark Wrap content with invisible Unicode sentinel markers
Scanner Parse sentinels; get(id) and hit(col, row) lookups
Zone Readonly bounding box (start/end col/row)
ZoneClickTracker Press+Release dedup per button
MouseEvent Immutable event (x, y, button, action enum)
MouseAction Press / Release / Drag / Scroll enum

Sentinel design

Sentinels use private-use codepoints U+E000 (open) and U+E001 (close) — they never collide with ANSI SGR sequences or regular text. Scanning strips them from output.

Multi-row zones

A zone spanning multiple rows (e.g. "line1\nline2") is stored as the smallest axis-aligned bounding box that contains all marked cells. The inBounds() check uses this rectangle, so an interior cell that was never part of the original content may still report as inside the zone. Callers wrapping reflowed or indented text should be aware that the hit-test is approximate for multi-line content.

See also

candy-zone is the TEA-facing façade (Manager, hover / drag / multi-click trackers, package-level Zones) layered over this low-level Mark/Scan/Zone primitive. It delegates its marker wrapping and bounding-box scanning to SugarCraft\Mouse — reach for candy-zone when you want the Bubble Tea-style zone workflow, and for candy-mouse directly when you just need self-contained hit-testing.

Coverage

codecov

Upstream

Inspired by lrstanley/bubblezone — the Mark/Scan/Get pattern mirrors bubblezone's API. ZoneClickTracker addresses bubblezone issue #10.