perf: replace O(n) hover loop with Clutter hit-test in motion-event

The previous implementation iterated all visible widgets on every
mouse-move event, calling get_transformed_position/size on each to
find which one was under the cursor. With a full page of items this
fires dozens of times per second on the compositor thread.

Use event.get_source() to let Clutter do its own hit-test (already
computed), then walk up the parent chain to find the owning
ClipboardItem. The walk is O(tree-depth), typically 3-4 steps,
regardless of history size.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Eduard Tolosa 2026-05-25 03:51:50 -05:00
parent 8d681a16fd
commit 575e216658

View file

@ -85,19 +85,18 @@ export class StrataPanel {
});
// pushModal intercepts all motion events at the overlay level, so CSS
// :hover never fires on child actors. Manually track which item is
// under the cursor and toggle the JS-driven 'strata-item-hovered' class.
// :hover never fires on child actors. Use event.get_source() to let
// Clutter do the hit-test, then walk up to find the owning ClipboardItem.
// This is O(tree-depth) instead of O(n-widgets) per mouse-move event.
this._overlay.connect('motion-event', (_actor, event) => {
const [cx, cy] = event.get_coords();
let source = event.get_source();
let found = null;
for (const widget of this._widgets.values()) {
if (!widget.visible) continue;
const [wx, wy] = widget.get_transformed_position();
const [ww, wh] = widget.get_transformed_size();
if (cx >= wx && cx <= wx + ww && cy >= wy && cy <= wy + wh) {
found = widget;
while (source && source !== this._overlay) {
if (source instanceof ClipboardItem) {
found = source;
break;
}
source = source.get_parent();
}
if (found !== this._hoveredWidget) {
this._hoveredWidget?.remove_style_class_name('strata-item-hovered');