handleMouseEvent method

void handleMouseEvent(
  1. MouseEvent event
)

Handles a mouse event, performing hit testing and routing to layers.

Implementation

void handleMouseEvent(MouseEvent event) {
  final meta = <String, String>{
    'button': event.button.name,
    'type': event.type.name,
    'pos': '${event.x},${event.y}',
  };
  if (event.modifiers.isNotEmpty) {
    meta['modifiers'] = event.modifiers.map((m) => m.name).join('+');
  }

  Tracer.record(
    _traceMouseEventId,
    Phase.begin,
    TraceCategory.events,
    metadata: meta,
  );
  try {
    _globalMouseX = event.x - 1;
    _globalMouseY = event.y - 1;
    _isGlobalMouseDown =
        event.type == MouseEventType.press ||
        event.type == MouseEventType.drag;

    if (debugShowTouchesEnabled) {
      if (event.type == MouseEventType.press ||
          event.type == MouseEventType.release) {
        _activeTouches.add(
          _TouchFeedback(
            x: _globalMouseX!,
            y: _globalMouseY!,
            button: event.button,
            type: event.type,
            startTime: clock.now().millisecondsSinceEpoch,
          ),
        );
        addRipple(Point<int>(event.x, event.y));
      }
    }

    var didRender = false;

    if (event.type == MouseEventType.press) {
      final mouseX = event.x - 1;
      final mouseY = event.y - 1;

      final sortedLayers = _getSortedLayers();

      SceneLayer? hitLayer;
      for (final layer in sortedLayers) {
        if (!layer.hitTestable) continue;

        final buf = layer.renderer.currentBuffer;
        if (buf == null) continue;

        if (mouseX >= layer.x &&
            mouseX < layer.x + buf.width &&
            mouseY >= layer.y &&
            mouseY < layer.y + buf.height) {
          final isTL = mouseX == layer.x && mouseY == layer.y;
          final isTR = mouseX == layer.x + buf.width - 1 && mouseY == layer.y;
          final isBL =
              mouseX == layer.x && mouseY == layer.y + buf.height - 1;
          final isBR =
              mouseX == layer.x + buf.width - 1 &&
              mouseY == layer.y + buf.height - 1;

          if (layer.resizable && (isTL || isTR || isBL || isBR)) {
            hitLayer = layer;
            _resizingLayer = layer;
            _resizeCorner = isTL
                ? 1
                : isTR
                ? 2
                : isBL
                ? 3
                : 4;
            _dragStartX = event.x;
            _dragStartY = event.y;
            _layerStartX = layer.x;
            _layerStartY = layer.y;
            _layerStartWidth = layer.width ?? buf.width;
            _layerStartHeight = layer.height ?? buf.height;
            break;
          }

          final localX = mouseX - layer.x;
          final localY = mouseY - layer.y;
          final localEvent = MouseEvent(
            x: localX + 1,
            y: localY + 1,
            globalX: event.globalX ?? event.x,
            globalY: event.globalY ?? event.y,
            button: event.button,
            type: event.type,
            modifiers: event.modifiers,
          );

          final isTransparent =
              (buf.getModifiers(localX, localY) & Modifier.transparent) != 0;
          final isOpaque = !isTransparent;

          final handled = layer.renderer.handleMouseEvent(localEvent);
          if (handled || layer.draggable) {
            hitLayer = layer;
            if (layer.draggable) {
              _draggingLayer = layer;
              _dragStartX = event.x;
              _dragStartY = event.y;
              _layerStartX = layer.x;
              _layerStartY = layer.y;
            }
            break;
          }

          if (isOpaque) {
            if (layer.mouseOpaque) {
              hitLayer = layer;
            }
            break;
          }
        }
      }

      if (hitLayer != null) {
        _capturedMouseLayer = hitLayer;
        if (focusedLayer != hitLayer) {
          focusedLayer = hitLayer;
          hitLayer.onFocus?.call();
          scheduleRender();
        }
      }
    } else if (event.type == MouseEventType.drag) {
      if (_resizingLayer != null) {
        int dx = event.x - _dragStartX;
        int dy = event.y - _dragStartY;

        if (_resizeCorner == 1 || _resizeCorner == 3) {
          if (_layerStartWidth - dx < 10) dx = _layerStartWidth - 10;
        } else {
          if (_layerStartWidth + dx < 10) dx = 10 - _layerStartWidth;
        }

        if (_resizeCorner == 1 || _resizeCorner == 2) {
          if (_layerStartHeight - dy < 5) dy = _layerStartHeight - 5;
        } else {
          if (_layerStartHeight + dy < 5) dy = 5 - _layerStartHeight;
        }

        var newX = _layerStartX;
        var newY = _layerStartY;
        var newW = _layerStartWidth;
        var newH = _layerStartHeight;

        if (_resizeCorner == 1) {
          newX += dx;
          newY += dy;
          newW -= dx;
          newH -= dy;
        } else if (_resizeCorner == 2) {
          newY += dy;
          newW += dx;
          newH -= dy;
        } else if (_resizeCorner == 3) {
          newX += dx;
          newW -= dx;
          newH += dy;
        } else if (_resizeCorner == 4) {
          newW += dx;
          newH += dy;
        }

        _resizingLayer!.x = newX;
        _resizingLayer!.y = newY;
        _resizingLayer!.width = newW;
        _resizingLayer!.height = newH;
        _resizeRenderer(_resizingLayer!.renderer, newW, newH);
        render();
        didRender = true;
      } else if (_draggingLayer != null) {
        final dx = event.x - _dragStartX;
        final dy = event.y - _dragStartY;
        _draggingLayer!.x = _layerStartX + dx;
        _draggingLayer!.y = _layerStartY + dy;
        render();
        didRender = true;
      } else if (_capturedMouseLayer != null) {
        final mouseX = event.x - 1;
        final mouseY = event.y - 1;
        final localX = mouseX - _capturedMouseLayer!.x;
        final localY = mouseY - _capturedMouseLayer!.y;
        final localEvent = MouseEvent(
          x: localX + 1,
          y: localY + 1,
          globalX: event.globalX ?? event.x,
          globalY: event.globalY ?? event.y,
          button: event.button,
          type: event.type,
          modifiers: event.modifiers,
        );
        _capturedMouseLayer!.renderer.handleMouseEvent(localEvent);
      }
    } else if (event.type == MouseEventType.release) {
      if (_resizingLayer != null) {
        _resizingLayer = null;
        _resizeCorner = 0;
        _capturedMouseLayer = null;
      } else {
        final targetLayer = _draggingLayer ?? _capturedMouseLayer;
        if (targetLayer != null) {
          final mouseX = event.x - 1;
          final mouseY = event.y - 1;
          final localX = mouseX - targetLayer.x;
          final localY = mouseY - targetLayer.y;
          final localEvent = MouseEvent(
            x: localX + 1,
            y: localY + 1,
            globalX: event.globalX ?? event.x,
            globalY: event.globalY ?? event.y,
            button: event.button,
            type: event.type,
            modifiers: event.modifiers,
          );
          targetLayer.renderer.handleMouseEvent(localEvent);
        }
        _draggingLayer = null;
        _capturedMouseLayer = null;
      }
    } else {
      // For hover (move) events, perform hit-test and route
      final mouseX = event.x - 1;
      final mouseY = event.y - 1;

      final sortedLayers = _getSortedLayers();
      SceneLayer? hitLayer;

      for (final layer in sortedLayers) {
        if (!layer.hitTestable) continue;

        final buf = layer.renderer.currentBuffer;
        if (buf == null) continue;

        if (mouseX >= layer.x &&
            mouseX < layer.x + buf.width &&
            mouseY >= layer.y &&
            mouseY < layer.y + buf.height) {
          final localX = mouseX - layer.x;
          final localY = mouseY - layer.y;
          final localEvent = MouseEvent(
            x: localX + 1,
            y: localY + 1,
            globalX: event.globalX ?? event.x,
            globalY: event.globalY ?? event.y,
            button: event.button,
            type: event.type,
            modifiers: event.modifiers,
          );

          final isTransparent =
              (buf.getModifiers(localX, localY) & Modifier.transparent) != 0;
          final isOpaque = !isTransparent;

          final handled = layer.renderer.handleMouseEvent(localEvent);
          if (handled) {
            hitLayer = layer;
            break;
          }

          if (isOpaque) {
            if (layer.mouseOpaque) {
              hitLayer = layer;
            }
            break;
          }
        }
      }

      if (_lastHoveredLayer != null && _lastHoveredLayer != hitLayer) {
        final localEvent = MouseEvent(
          x: -1,
          y: -1,
          globalX: event.globalX ?? event.x,
          globalY: event.globalY ?? event.y,
          button: event.button,
          type: event.type,
          modifiers: event.modifiers,
        );
        _lastHoveredLayer!.renderer.handleMouseEvent(localEvent);
      }
      _lastHoveredLayer = hitLayer;
    }

    if (debugMouseCursorEnabled && !didRender) {
      render();
    }
  } finally {
    Tracer.record(_traceMouseEventId, Phase.end, TraceCategory.events);
  }
}