# 事件处理

PE引擎提供了简洁而强大的事件处理机制，让你能够轻松响应用户的交互操作。事件处理是创建交互式应用的关键部分。

## 事件绑定语法

在PE的场景模板中，你可以使用`@`前缀直接绑定事件处理器：

```html
<sence>
  <!-- 基本事件绑定 -->
  <box @click="handleClick">点击我</box>
  
  <!-- 多个事件绑定 -->
  <box @click="handleClick" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
    悬停我
  </box>
  
  <!-- 传递参数 -->
  <box @click="handleClickWithParam('button1')">按钮1</box>
  <box @click="handleClickWithParam('button2')">按钮2</box>
</sence>

<script>
function handleClick() {
  console.log('元素被点击了')
}

function handleMouseEnter() {
  console.log('鼠标进入元素')
}

function handleMouseLeave() {
  console.log('鼠标离开元素')
}

function handleClickWithParam(buttonId) {
  console.log('点击了按钮:', buttonId)
}
</script>
```

## 支持的事件类型

PE支持所有标准的DOM事件类型：

### 鼠标事件

```html
<sence>
  <box @click="handleClick"
       @dblclick="handleDoubleClick"
       @mouseenter="handleMouseEnter"
       @mouseleave="handleMouseLeave"
       @mousedown="handleMouseDown"
       @mouseup="handleMouseUp"
       @contextmenu="handleContextMenu">
    鼠标事件示例
  </box>
</sence>

<script>
function handleClick(event) {
  console.log('点击事件:', event)
}

function handleDoubleClick(event) {
  console.log('双击事件:', event)
}

function handleMouseEnter(event) {
  console.log('鼠标进入:', event)
}

function handleMouseLeave(event) {
  console.log('鼠标离开:', event)
}

function handleMouseDown(event) {
  console.log('鼠标按下:', event)
}

function handleMouseUp(event) {
  console.log('鼠标释放:', event)
}

function handleContextMenu(event) {
  event.preventDefault() // 阻止右键菜单
  console.log('右键点击:', event)
}
</script>
```

### 键盘事件

```html
<sence>
  <text @keydown="handleKeyDown"
        @keyup="handleKeyUp"
        @keypress="handleKeyPress"
        tabindex="0">
    按键事件示例（点击后按键盘）
  </text>
</sence>

<script>
function handleKeyDown(event) {
  console.log('按键按下:', event.key, event.code)
}

function handleKeyUp(event) {
  console.log('按键释放:', event.key, event.code)
}

function handleKeyPress(event) {
  console.log('按键按压:', event.key)
}
</script>
```

### 表单事件

```html
<sence>
  <html @input="handleInput" 
        @change="handleChange"
        @focus="handleFocus"
        @blur="handleBlur"
        html="<input type='text' placeholder='输入内容...' />">
  </html>
</sence>

<script>
function handleInput(event) {
  console.log('输入事件:', event.target.value)
}

function handleChange(event) {
  console.log('值改变事件:', event.target.value)
}

function handleFocus(event) {
  console.log('获得焦点')
}

function handleBlur(event) {
  console.log('失去焦点')
}
</script>
```

### 触摸事件

```html
<sence>
  <box @touchstart="handleTouchStart"
       @touchmove="handleTouchMove"
       @touchend="handleTouchEnd"
       @touchcancel="handleTouchCancel">
    触摸事件示例
  </box>
</sence>

<script>
function handleTouchStart(event) {
  console.log('触摸开始:', event.touches.length, '个触摸点')
}

function handleTouchMove(event) {
  console.log('触摸移动:', event.touches[0].clientX, event.touches[0].clientY)
}

function handleTouchEnd(event) {
  console.log('触摸结束')
}

function handleTouchCancel(event) {
  console.log('触摸取消')
}
</script>
```

## 事件对象

所有事件处理器都会接收到一个事件对象，包含有关事件的详细信息：

```javascript
function handleClick(event) {
  // 事件基本信息
  console.log('事件类型:', event.type)
  console.log('目标元素:', event.target)
  console.log('当前元素:', event.currentTarget)
  
  // 鼠标位置
  console.log('页面坐标:', event.pageX, event.pageY)
  console.log('客户端坐标:', event.clientX, event.clientY)
  
  // 键盘信息
  console.log('是否按下Ctrl键:', event.ctrlKey)
  console.log('是否按下Alt键:', event.altKey)
  console.log('是否按下Shift键:', event.shiftKey)
  
  // 阻止默认行为
  event.preventDefault()
  
  // 阻止事件冒泡
  event.stopPropagation()
}
```

## 在JavaScript中绑定事件

除了在模板中绑定事件，你也可以在JavaScript代码中直接绑定事件：

```javascript
// 获取场景元素
const element = game.getSceneElement('my-button')

// 添加事件监听器
element.element.addEventListener('click', function(event) {
  console.log('元素被点击了')
})

// 添加多个事件监听器
element.element.addEventListener('mouseenter', handleMouseEnter)
element.element.addEventListener('mouseleave', handleMouseLeave)

// 移除事件监听器
element.element.removeEventListener('click', handleClick)
```

## 事件修饰符

PE支持一些常用的事件修饰符，类似于Vue.js的语法：

```html
<sence>
  <!-- 阻止默认行为 -->
  <box @click.prevent="handleClick">阻止默认行为</box>
  
  <!-- 阻止事件冒泡 -->
  <box @click.stop="handleClick">阻止事件冒泡</box>
  
  <!-- 只触发一次 -->
  <box @click.once="handleClick">只触发一次</box>
  
  <!-- 组合修饰符 -->
  <box @click.stop.prevent="handleClick">阻止冒泡和默认行为</box>
</sence>
```

## 自定义事件

你也可以创建和触发自定义事件：

```javascript
// 创建自定义事件
const customEvent = new CustomEvent('myCustomEvent', {
  detail: { message: '这是自定义事件数据' }
})

// 触发自定义事件
element.element.dispatchEvent(customEvent)

// 监听自定义事件
element.element.addEventListener('myCustomEvent', function(event) {
  console.log('接收到自定义事件:', event.detail.message)
})
```

## 事件总线

PE引擎内置了事件总线，允许你在不同组件间进行通信：

```javascript
// 发送事件
game.eventBus.emit('user-login', { username: 'john' })

// 监听事件
game.eventBus.on('user-login', function(data) {
  console.log('用户登录:', data.username)
})

// 移除事件监听器
game.eventBus.off('user-login', handlerFunction)
```

## 在场景脚本中处理事件

在场景的`<script>`部分，你可以定义事件处理函数：

```html
<sence>
  <box class="counter-button" @click="incrementCounter">计数器: {{ counter }}</box>
  <box class="reset-button" @click="resetCounter">重置</box>
</sence>

<script>
// 数据定义
let counter = 0

// 事件处理函数
function incrementCounter() {
  counter++
  console.log('计数器:', counter)
  
  // 更新UI
  const button = game.getSceneElement('counter-button')
  button.element.textContent = `计数器: ${counter}`
}

function resetCounter() {
  counter = 0
  console.log('计数器已重置')
  
  // 更新UI
  const button = game.getSceneElement('counter-button')
  button.element.textContent = `计数器: ${counter}`
}

// 生命周期钩子中绑定事件
onLoad(() => {
  console.log('场景加载完成，计数器初始化为', counter)
})

onShow(() => {
  console.log('场景显示，当前计数器值为', counter)
})
</script>
```

## 事件处理最佳实践

### 1. 合理使用事件委托

对于大量相似元素，使用事件委托可以提高性能：

```html
<sence>
  <!-- 使用事件委托处理列表项点击 -->
  <box class="list-container" @click="handleListItemClick">
    <box class="list-item" data-id="1">项目1</box>
    <box class="list-item" data-id="2">项目2</box>
    <box class="list-item" data-id="3">项目3</box>
  </box>
</sence>

<script>
function handleListItemClick(event) {
  // 检查点击的是否是列表项
  if (event.target.classList.contains('list-item')) {
    const id = event.target.getAttribute('data-id')
    console.log('点击了项目:', id)
  }
}
</script>
```

### 2. 及时移除事件监听器

在适当的时机移除事件监听器，避免内存泄漏：

```javascript
onLoad(() => {
  // 添加事件监听器
  document.addEventListener('keydown', handleGlobalKeyDown)
})

onDestory(() => {
  // 移除事件监听器
  document.removeEventListener('keydown', handleGlobalKeyDown)
})

function handleGlobalKeyDown(event) {
  // 全局键盘事件处理
  if (event.key === 'Escape') {
    PE.navigateTo('/')
  }
}
```

### 3. 使用防抖和节流

对于频繁触发的事件（如滚动、输入），使用防抖和节流优化性能：

```javascript
// 防抖函数
function debounce(func, wait) {
  let timeout
  return function(...args) {
    clearTimeout(timeout)
    timeout = setTimeout(() => func.apply(this, args), wait)
  }
}

// 节流函数
function throttle(func, limit) {
  let inThrottle
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args)
      inThrottle = true
      setTimeout(() => inThrottle = false, limit)
    }
  }
}

// 使用防抖处理输入事件
const handleInput = debounce(function(event) {
  console.log('输入内容:', event.target.value)
}, 300)

// 使用节流处理滚动事件
const handleScroll = throttle(function(event) {
  console.log('页面滚动')
}, 100)
```

## 完整示例

以下是一个完整的事件处理示例：

### scenes/game/index.pe

```html
<sence>
  <box class="game-container">
    <!-- 玩家角色 -->
    <sprite class="player" 
            @mousedown="startDrag" 
            @mouseup="stopDrag"
            @touchstart="startDrag"
            @touchend="stopDrag">
    </sprite>
    
    <!-- 敌人 -->
    <sprite for="{enemy} in enemies" 
            class="enemy" 
            :class="{ 'enemy-active': enemy.active }"
            @click="hitEnemy(enemy.id)">
    </sprite>
    
    <!-- 控制按钮 -->
    <box class="control-panel">
      <box class="btn btn-primary" @click="startGame">开始游戏</box>
      <box class="btn btn-secondary" @click="pauseGame">暂停</box>
      <box class="btn btn-danger" @click="resetGame">重置</box>
    </box>
    
    <!-- 游戏信息 -->
    <text class="score">得分: {{ score }}</text>
    <text class="lives">生命: {{ lives }}</text>
  </box>
</sence>

<script>
// 游戏状态
let score = 0
let lives = 3
let gameRunning = false
let dragging = false

// 敌人数据
const enemies = [
  { id: 1, active: true },
  { id: 2, active: true },
  { id: 3, active: true }
]

// 事件处理函数
function startDrag(event) {
  if (!gameRunning) return
  dragging = true
  console.log('开始拖拽玩家')
  
  // 添加鼠标移动事件监听器
  document.addEventListener('mousemove', handleDrag)
  document.addEventListener('touchmove', handleDrag)
}

function stopDrag(event) {
  dragging = false
  console.log('停止拖拽玩家')
  
  // 移除鼠标移动事件监听器
  document.removeEventListener('mousemove', handleDrag)
  document.removeEventListener('touchmove', handleDrag)
}

function handleDrag(event) {
  if (!dragging || !gameRunning) return
  
  const player = game.getSceneElement('player')
  if (player) {
    // 获取鼠标或触摸位置
    let x, y
    if (event.type === 'touchmove') {
      x = event.touches[0].clientX
      y = event.touches[0].clientY
    } else {
      x = event.clientX
      y = event.clientY
    }
    
    // 更新玩家位置
    player.setPosition(x - 25, y - 25) // 考虑精灵尺寸
  }
}

function hitEnemy(enemyId) {
  if (!gameRunning) return
  
  // 找到被击中的敌人
  const enemy = enemies.find(e => e.id === enemyId)
  if (enemy && enemy.active) {
    // 标记敌人为非活跃状态
    enemy.active = false
    
    // 增加得分
    score += 10
    updateScore()
    
    console.log('击中敌人:', enemyId)
  }
}

function startGame() {
  gameRunning = true
  score = 0
  lives = 3
  updateScore()
  updateLives()
  
  // 重置敌人状态
  enemies.forEach(enemy => {
    enemy.active = true
  })
  
  console.log('游戏开始')
}

function pauseGame() {
  gameRunning = !gameRunning
  console.log(gameRunning ? '游戏继续' : '游戏暂停')
}

function resetGame() {
  gameRunning = false
  score = 0
  lives = 3
  updateScore()
  updateLives()
  
  // 重置敌人状态
  enemies.forEach(enemy => {
    enemy.active = true
  })
  
  console.log('游戏重置')
}

// 更新得分显示
function updateScore() {
  const scoreElement = game.getSceneElement('score')
  if (scoreElement) {
    scoreElement.element.textContent = `得分: ${score}`
  }
}

// 更新生命显示
function updateLives() {
  const livesElement = game.getSceneElement('lives')
  if (livesElement) {
    livesElement.element.textContent = `生命: ${lives}`
  }
}

// 生命周期钩子
onLoad(() => {
  console.log('游戏场景加载完成')
})

onShow(() => {
  console.log('游戏场景显示')
  // 初始化游戏状态
  updateScore()
  updateLives()
})

onHide(() => {
  console.log('游戏场景隐藏')
  // 暂停游戏
  gameRunning = false
})

onDestory(() => {
  console.log('游戏场景销毁')
  // 清理事件监听器
  document.removeEventListener('mousemove', handleDrag)
  document.removeEventListener('touchmove', handleDrag)
})
</script>
```

通过以上内容，你已经了解了PE引擎的事件处理机制。在下一章节中，我们将探讨生命周期的相关内容。