下面是基于golang的ebiten引擎设计的植物大战僵尸游戏 的UI模块的子模块,请全面评估实现是否合理,有什么潜在 问题?有什么需要优化的?
# File: interactive_test.go
```go
package base
import (
"testing"
"plants-vs-zombies/internal/core/event"
"github.com/stretchr/testify/assert"
)
func TestInteractiveComponent(t *testing.T) {
t.Run("MouseEvents", func(t *testing.T) {
comp := NewInteractiveComponent()
comp.SetBounds(NewBounds(0, 0, 100, 100))
// 测试鼠标进入/离开
mouseEntered := false
mouseLeft := false
handler := NewEventHandler(func(e event.Event) bool {
mouseEntered = true
return true
})
comp.AddEventListener(event.EventTypeMouseEnter, event.EventPhaseTarget, handler, 0)
handler = NewEventHandler(func(e event.Event) bool {
mouseLeft = true
return true
})
comp.AddEventListener(event.EventTypeMouseLeave, event.EventPhaseTarget, handler, 0)
// 鼠标进入
evt := event.NewMouseEvent(event.EventTypeMouseMove, 50, 50)
comp.HandleEvent(evt)
assert.True(t, comp.IsHovered())
assert.True(t, mouseEntered)
// 鼠标离开
evt = event.NewMouseEvent(event.EventTypeMouseMove, 150, 150)
comp.HandleEvent(evt)
assert.False(t, comp.IsHovered())
assert.True(t, mouseLeft)
})
t.Run("MouseButtonEvents", func(t *testing.T) {
comp := NewInteractiveComponent()
comp.SetBounds(NewBounds(0, 0, 100, 100))
// 测试鼠标按下/释放
buttonDown := false
buttonUp := false
clicked := false
handler := NewEventHandler(func(e event.Event) bool {
buttonDown = true
return true
})
comp.AddEventListener(event.EventTypeMouseButtonDown, event.EventPhaseTarget, handler, 0)
handler = NewEventHandler(func(e event.Event) bool {
buttonUp = true
return true
})
comp.AddEventListener(event.EventTypeMouseButtonUp, event.EventPhaseTarget, handler, 0)
handler = NewEventHandler(func(e event.Event) bool {
clicked = true
return true
})
comp.AddEventListener(event.EventTypeClick, event.EventPhaseTarget, handler, 0)
// 先移动鼠标到组件上
evt := event.NewMouseEvent(event.EventTypeMouseMove, 50, 50)
comp.HandleEvent(evt)
// 鼠标按下
evt = event.NewMouseEvent(event.EventTypeMouseButtonDown, 50, 50)
comp.HandleEvent(evt)
assert.True(t, comp.IsPressed())
assert.True(t, buttonDown)
// 鼠标释放
evt = event.NewMouseEvent(event.EventTypeMouseButtonUp, 50, 50)
comp.HandleEvent(evt)
assert.False(t, comp.IsPressed())
assert.True(t, buttonUp)
assert.True(t, clicked)
})
t.Run("StateChanges", func(t *testing.T) {
comp := NewInteractiveComponent()
comp.SetBounds(NewBounds(0, 0, 100, 100))
// 设置悬停状态
evt := event.NewMouseEvent(event.EventTypeMouseMove, 50, 50)
comp.HandleEvent(evt)
assert.True(t, comp.IsHovered())
// 禁用组件
comp.SetEnabled(false)
assert.False(t, comp.IsHovered())
assert.False(t, comp.IsPressed())
assert.False(t, comp.IsFocused())
// 隐藏组件
comp.SetEnabled(true)
comp.SetVisible(false)
assert.False(t, comp.IsHovered())
assert.False(t, comp.IsPressed())
assert.False(t, comp.IsFocused())
})
t.Run("FocusManagement", func(t *testing.T) {
comp := NewInteractiveComponent()
comp.SetBounds(NewBounds(0, 0, 100, 100))
focusGained := false
focusLost := false
handler := NewEventHandler(func(e event.Event) bool {
focusGained = true
return true
})
comp.AddEventListener(event.EventTypeFocusGain, event.EventPhaseTarget, handler, 0)
handler = NewEventHandler(func(e event.Event) bool {
focusLost = true
return true
})
comp.AddEventListener(event.EventTypeFocusLoss, event.EventPhaseTarget, handler, 0)
// 通过鼠标点击获得焦点
evt := event.NewMouseEvent(event.EventTypeMouseButtonDown, 50, 50)
comp.HandleEvent(evt)
assert.True(t, comp.IsFocused())
assert.True(t, focusGained)
// 禁用时失去焦点
comp.SetEnabled(false)
assert.False(t, comp.IsFocused())
assert.True(t, focusLost)
})
t.Run("EventPropagation", func(t *testing.T) {
parent := NewInteractiveComponent()
child := NewInteractiveComponent()
parent.AddChild(child)
child.SetBounds(NewBounds(0, 0, 100, 100))
var eventOrder []string
parentHandled := false
childHandled := false
handler := NewEventHandler(func(e event.Event) bool {
eventOrder = append(eventOrder, "parent")
parentHandled = true
return true
})
parent.AddEventListener(event.EventTypeMouseMove, event.EventPhaseTarget, handler, 0)
handler = NewEventHandler(func(e event.Event) bool {
eventOrder = append(eventOrder, "child")
childHandled = true
return true
})
child.AddEventListener(event.EventTypeMouseMove, event.EventPhaseTarget, handler, 0)
// 触发子组件事件
evt := event.NewMouseEvent(event.EventTypeMouseMove, 50, 50)
evt.SetBubbles(true) // Set event to bubble
child.HandleEvent(evt)
assert.True(t, childHandled)
assert.True(t, parentHandled)
assert.Equal(t, []string{"child", "parent"}, eventOrder)
})
}
```
# File: bounds_test.go
```go
package base
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestBaseBounds(t *testing.T) {
t.Run("Creation", func(t *testing.T) {
bounds := NewBounds(10, 20, 100, 200)
assert.Equal(t, float64(10), bounds.X())
assert.Equal(t, float64(20), bounds.Y())
assert.Equal(t, float64(100), bounds.Width())
assert.Equal(t, float64(200), bounds.Height())
})
t.Run("Setters", func(t *testing.T) {
bounds := NewBounds(0, 0, 0, 0)
bounds.SetX(10)
bounds.SetY(20)
bounds.SetWidth(100)
bounds.SetHeight(200)
assert.Equal(t, float64(10), bounds.X())
assert.Equal(t, float64(20), bounds.Y())
assert.Equal(t, float64(100), bounds.Width())
assert.Equal(t, float64(200), bounds.Height())
})
t.Run("Contains", func(t *testing.T) {
bounds := NewBounds(10, 20, 100, 200)
// 测试边界内的点
assert.True(t, bounds.Contains(10, 20)) // 左上角
assert.True(t, bounds.Contains(110, 220)) // 右下角
assert.True(t, bounds.Contains(60, 120)) // 中心点
// 测试边界外的点
assert.False(t, bounds.Contains(9, 20)) // 左边界外
assert.False(t, bounds.Contains(111, 120)) // 右边界外
assert.False(t, bounds.Contains(60, 19)) // 上边界外
assert.False(t, bounds.Contains(60, 221)) // 下边界外
})
t.Run("Intersects", func(t *testing.T) {
bounds1 := NewBounds(10, 20, 100, 200)
bounds2 := NewBounds(50, 60, 100, 200) // 相交
bounds3 := NewBounds(200, 300, 100, 200) // 不相交
assert.True(t, bounds1.Intersects(bounds2))
assert.True(t, bounds2.Intersects(bounds1))
assert.False(t, bounds1.Intersects(bounds3))
assert.False(t, bounds3.Intersects(bounds1))
})
t.Run("Clone", func(t *testing.T) {
bounds := NewBounds(10, 20, 100, 200)
clone := bounds.Clone()
assert.Equal(t, bounds.X(), clone.X())
assert.Equal(t, bounds.Y(), clone.Y())
assert.Equal(t, bounds.Width(), clone.Width())
assert.Equal(t, bounds.Height(), clone.Height())
// 修改克隆不应影响原对象
clone.SetX(30)
assert.NotEqual(t, bounds.X(), clone.X())
})
t.Run("String", func(t *testing.T) {
bounds := NewBounds(10, 20, 100, 200)
expected := "Bounds(x:10.0, y:20.0, w:100.0, h:200.0)"
assert.Equal(t, expected, bounds.String())
})
}
```
# File: container.go
```go
package base
import (
"fmt"
"plants-vs-zombies/internal/core/event"
"sync"
)
// BaseContainer 提供了容器组件的基本实现
type BaseContainer struct {
*BaseComponent
layout SimpleLayout
padding *Margins
margin *Margins
debugger *LayoutDebugger
// 用于跟踪事件处理,防止递归
eventProcessing sync.Map
}
// NewBaseContainer 创建新的容器组件
func NewBaseContainer() *BaseContainer {
c := &BaseContainer{
BaseComponent: NewBaseComponent(),
padding: NewMargins(0, 0, 0, 0),
margin: NewMargins(0, 0, 0, 0),
}
return c
}
// GetPadding 获取内边距
func (c *BaseContainer) GetPadding() *Margins {
c.RLock()
defer c.RUnlock()
return c.padding.Clone()
}
// SetPadding 设置内边距
func (c *BaseContainer) SetPadding(padding *Margins) {
c.Lock()
defer c.Unlock()
if padding != nil {
c.padding = padding.Clone()
if c.layout != nil {
c.layout.Layout(c)
}
}
}
// GetMargin 获取外边距
func (c *BaseContainer) GetMargin() *Margins {
c.RLock()
defer c.RUnlock()
return c.margin.Clone()
}
// SetMargin 设置外边距
func (c *BaseContainer) SetMargin(margin *Margins) {
c.Lock()
defer c.Unlock()
if margin != nil {
c.margin = margin.Clone()
if c.layout != nil {
c.layout.Layout(c)
}
}
}
// Clear 清除所有子组件
func (c *BaseContainer) Clear() {
c.Lock()
defer c.Unlock()
c.BaseComponent.Clear()
if c.layout != nil {
c.layout.Layout(c)
}
}
// Layout 执行布局
func (c *BaseContainer) Layout() {
if c.layout != nil {
c.layout.Layout(c)
}
}
// AddComponent 添加组件
func (c *BaseContainer) AddComponent(component Component) {
c.Lock()
defer c.Unlock()
c.BaseComponent.AddChild(component)
if c.layout != nil {
c.layout.Layout(c)
}
}
// RemoveComponent 移除组件
func (c *BaseContainer) RemoveComponent(component Component) {
c.Lock()
defer c.Unlock()
c.BaseComponent.RemoveChild(component)
if c.layout != nil {
c.layout.Layout(c)
}
}
// GetComponents 获取所有子组件
func (c *BaseContainer) GetComponents() []Component {
c.RLock()
defer c.RUnlock()
return c.BaseComponent.Children()
}
// HandleEvent 处理事件
func (c *BaseContainer) HandleEvent(evt event.Event) bool {
// 防止重复处理
eventKey := fmt.Sprintf("%p-%s", evt, c.id)
if _, exists := c.eventProcessing.LoadOrStore(eventKey, true); exists {
return false
}
defer c.eventProcessing.Delete(eventKey)
// 如果组件被禁用或隐藏,不处理事件
if !c.IsEnabled() || !c.IsVisible() {
return false
}
// 如果没有设置事件目标,将当前组件设为目标
if evt.Target() == nil {
evt.SetTarget(c.eventTarget)
evt.SetPhase(event.EventPhaseCapturing)
}
// 根据事件阶段处理
switch evt.Phase() {
case event.EventPhaseCapturing:
// 先处理自己的捕获阶段监听器
if c.BaseComponent.HandleEvent(evt) {
return true
}
// 如果当前组件是目标,切换到目标阶段
if c.eventTarget == evt.Target() {
evt.SetPhase(event.EventPhaseTarget)
return c.HandleEvent(evt)
}
// 向下传播到子组件
var children []Component
func() {
c.RLock()
defer c.RUnlock()
children = make([]Component, len(c.children))
copy(children, c.children)
}()
for _, child := range children {
// 检查子组件是否是目标
var isTarget bool
if bc, ok := child.(*BaseComponent); ok {
isTarget = bc.eventTarget == evt.Target()
}
// 检查子组件是否是容器并且包含目标
var containsTarget bool
if container, ok := child.(*BaseContainer); ok {
containsTarget = container.Contains(0, 0)
}
// 如果子��件是目标或者包含目标,则传播事件
if isTarget || containsTarget {
if child.HandleEvent(evt) {
return true
}
}
}
case event.EventPhaseTarget:
// 处理目标阶段
handled := c.BaseComponent.HandleEvent(evt)
// 如果事件可以冒泡,切换到冒泡阶段并开始冒泡
if evt.Bubbles() {
evt.SetPhase(event.EventPhaseBubbling)
if c.parent != nil {
parentHandled := c.parent.HandleEvent(evt)
return handled || parentHandled
}
}
return handled
case event.EventPhaseBubbling:
// 处理冒泡阶段
if !evt.Bubbles() {
return false
}
// 处理自己的冒泡阶段监听器
handled := c.BaseComponent.HandleEvent(evt)
if handled {
return true
}
// 继续向上冒泡
if c.parent != nil {
return c.parent.HandleEvent(evt)
}
}
return false
}
// findEventPath 查找从当前组件到目标组件的路径
func (c *BaseContainer) findEventPath(target event.EventTarget) []Component {
// 如果当前组件是目标
if c.eventTarget == target {
return []Component{c}
}
// 遍历子组件查找路径
for _, child := range c.GetComponents() {
// 如果子组件是目标
if child.(*BaseComponent).eventTarget == target {
return []Component{child}
}
// 如果子组件是容器,递归查找
if container, ok := child.(*BaseContainer); ok {
if path := container.findEventPath(target); len(path) > 0 {
return append([]Component{child}, path...)
}
}
}
return nil
}
// EmitEvent 发送事件
func (c *BaseContainer) EmitEvent(eventType event.EventType) {
evt := event.NewBaseEvent(eventType, true)
evt.SetTarget(c.eventTarget)
// 查找根组件
var root Component = c
var parent Component
// 使用函数闭包限制锁的范围
func() {
c.RLock()
defer c.RUnlock()
parent = c.parent
}()
// 在锁外查找根组件
for parent != nil {
root = parent
parent = parent.Parent()
}
// 设置初始阶段并开始传播
evt.SetPhase(event.EventPhaseCapturing)
root.HandleEvent(evt)
}
// findRoot 查找根组件
func (c *BaseContainer) findRoot() Component {
current := Component(c)
for p := c.parent; p != nil; p = p.Parent() {
current = p
}
return current
}
// Contains 检查点是否在容器内
func (c *BaseContainer) Contains(x, y float64) bool {
// 先检查自己的边界
if !c.bounds.Contains(x, y) {
return false
}
// 再检查子组件
for _, child := range c.GetComponents() {
if child.GetBounds().Contains(x, y) {
return true
}
}
return true
}
// SetLayout 设置容器的布局管理器
func (c *BaseContainer) SetLayout(layout SimpleLayout) {
c.Lock()
defer c.Unlock()
c.layout = layout
}
// GetLayout 获取容器的布局管理器
func (c *BaseContainer) GetLayout() SimpleLayout {
c.RLock()
defer c.RUnlock()
return c.layout
}
// SetDebugger 设置布局调试器
func (c *BaseContainer) SetDebugger(debugger *LayoutDebugger) {
c.Lock()
defer c.Unlock()
c.debugger = debugger
}
// GetDebugger 获取布局调试器
func (c *BaseContainer) GetDebugger() *LayoutDebugger {
c.RLock()
defer c.RUnlock()
return c.debugger
}
```
# File: component_test.go
```go
package base
import (
"sync"
"testing"
"plants-vs-zombies/internal/core/event"
"github.com/stretchr/testify/assert"
)
func TestBaseComponent(t *testing.T) {
t.Run("Initialization", func(t *testing.T) {
comp := NewBaseComponent()
assert.NotNil(t, comp)
assert.True(t, comp.IsEnabled())
assert.True(t, comp.IsVisible())
assert.False(t, comp.IsFocused())
assert.NotNil(t, comp.GetBounds())
assert.Empty(t, comp.Children())
})
t.Run("StateManagement", func(t *testing.T) {
comp := NewBaseComponent()
// 测试启用/禁用
comp.SetEnabled(false)
assert.False(t, comp.IsEnabled())
comp.SetEnabled(true)
assert.True(t, comp.IsEnabled())
// 测试显示/隐藏
comp.SetVisible(false)
assert.False(t, comp.IsVisible())
comp.SetVisible(true)
assert.True(t, comp.IsVisible())
// 测试焦点
comp.SetFocused(true)
assert.True(t, comp.IsFocused())
comp.SetFocused(false)
assert.False(t, comp.IsFocused())
})
t.Run("ComponentTree", func(t *testing.T) {
parent := NewBaseComponent()
child1 := NewBaseComponent()
child2 := NewBaseComponent()
// 测试添加子组件
parent.AddChild(child1)
parent.AddChild(child2)
assert.Equal(t, 2, len(parent.Children()))
assert.Equal(t, parent, child1.Parent())
assert.Equal(t, parent, child2.Parent())
// 测试移除子组件
parent.RemoveChild(child1)
assert.Equal(t, 1, len(parent.Children()))
assert.Nil(t, child1.Parent())
assert.Equal(t, parent, child2.Parent())
})
t.Run("EventHandling", func(t *testing.T) {
comp := NewBaseComponent()
handled := false
// 添加事件监听器
handler := NewEventHandler(func(e event.Event) bool {
handled = true
return true
})
comp.AddEventListener(event.EventTypeMouseMove, event.EventPhaseTarget, handler, 0)
// 触发事件
evt := event.NewBaseEvent(event.EventTypeMouseMove, true)
evt.SetTarget(comp.eventTarget)
comp.DispatchEvent(evt)
assert.True(t, handled)
})
t.Run("Layout", func(t *testing.T) {
container := NewBaseComponent()
container.SetBounds(NewBounds(0, 0, 200, 200))
child := NewBaseComponent()
child.SetBounds(NewBounds(0, 0, 50, 50))
container.AddChild(child)
// 测试流式布局
layout := NewSimpleFlowLayout(10, Horizontal)
container.SetLayout(layout)
container.Layout()
assert.Equal(t, float64(0), child.GetBounds().X())
assert.Equal(t, float64(0), child.GetBounds().Y())
})
}
func TestComponentConcurrency(t *testing.T) {
t.Run("ConcurrentChildOperations", func(t *testing.T) {
// 测试并发添加/删除子组件的操作
parent := NewBaseComponent()
var wg sync.WaitGroup
// 并发添加和删除子组件
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
child := NewBaseComponent()
parent.AddChild(child)
parent.RemoveChild(child)
}()
}
wg.Wait()
assert.Empty(t, parent.Children())
})
t.Run("ConcurrentStateModification", func(t *testing.T) {
comp := NewBaseComponent()
var wg sync.WaitGroup
iterations := 1000
for i := 0; i < iterations; i++ {
wg.Add(1)
go func() {
defer wg.Done()
comp.SetEnabled(true)
comp.SetVisible(true)
comp.SetFocused(true)
_ = comp.IsEnabled()
_ = comp.IsVisible()
_ = comp.IsFocused()
}()
}
wg.Wait()
})
t.Run("ConcurrentEventHandling", func(t *testing.T) {
comp := NewBaseComponent()
var wg sync.WaitGroup
eventCount := 100
// 添加事件处理器
handler := NewEventHandler(func(e event.Event) bool {
return true
})
comp.AddEventListener(event.EventTypeMouseMove, event.EventPhaseTarget, handler, 0)
// 并发触发事件
for i := 0; i < eventCount; i++ {
wg.Add(1)
go func() {
defer wg.Done()
evt := event.NewBaseEvent(event.EventTypeMouseMove, true)
evt.SetTarget(comp.eventTarget)
comp.DispatchEvent(evt)
}()
}
wg.Wait()
})
}
func TestComponentLifecycle(t *testing.T) {
t.Run("InitAndDestroy", func(t *testing.T) {
parent := NewBaseComponent()
child := NewBaseComponent()
parent.AddChild(child)
// 测试初始化
parent.Init()
assert.True(t, parent.initialized)
assert.True(t, child.initialized)
// 测试销毁
parent.Destroy()
assert.Empty(t, parent.Children())
assert.Nil(t, child.Parent())
assert.Nil(t, parent.eventTarget)
})
t.Run("ComponentPool", func(t *testing.T) {
// 从池中获取组件
comp := GetComponent()
assert.NotNil(t, comp)
assert.True(t, comp.IsEnabled())
assert.True(t, comp.IsVisible())
// 修改组件状态
comp.SetEnabled(false)
comp.SetVisible(false)
child := NewBaseComponent()
comp.AddChild(child)
// 放回池中
PutComponent(comp)
// 再次获取,验证状态已重置
comp2 := GetComponent()
assert.True(t, comp2.IsEnabled())
assert.True(t, comp2.IsVisible())
assert.Empty(t, comp2.Children())
})
}
func BenchmarkComponent(b *testing.B) {
b.Run("ChildOperations", func(b *testing.B) {
parent := NewBaseComponent()
child := NewBaseComponent()
b.ResetTimer()
for i := 0; i < b.N; i++ {
parent.AddChild(child)
parent.RemoveChild(child)
}
})
b.Run("EventDispatch", func(b *testing.B) {
comp := NewBaseComponent()
evt := event.NewBaseEvent(event.EventTypeMouseMove, true)
evt.SetTarget(comp.eventTarget)
handler := NewEventHandler(func(e event.Event) bool {
return true
})
comp.AddEventListener(event.EventTypeMouseMove, event.EventPhaseTarget, handler, 0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
comp.DispatchEvent(evt)
}
})
b.Run("ComponentPool", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
comp := GetComponent()
PutComponent(comp)
}
})
}
```
# File: lifecycle.go
```go
package base
// LifecycleState represents the current state of a component's lifecycle
type LifecycleState int
const (
// LifecycleStateUnmounted indicates the component is not mounted
LifecycleStateUnmounted LifecycleState = iota
// LifecycleStateMounting indicates the component is in the process of mounting
LifecycleStateMounting
// LifecycleStateMounted indicates the component is mounted and active
LifecycleStateMounted
// LifecycleStateUnmounting indicates the component is in the process of unmounting
LifecycleStateUnmounting
)
// LifecycleAware defines the interface for components that need lifecycle management
type LifecycleAware interface {
// GetLifecycleState returns the current lifecycle state
GetLifecycleState() LifecycleState
// SetLifecycleState sets the lifecycle state
SetLifecycleState(state LifecycleState)
// Lifecycle hooks
BeforeMount()
AfterMount()
BeforeUnmount()
AfterUnmount()
// Update is called when the component needs to update
Update()
}
// LifecycleManager provides lifecycle management functionality
type LifecycleManager struct {
state LifecycleState
}
// NewLifecycleManager creates a new lifecycle manager
func NewLifecycleManager() *LifecycleManager {
return &LifecycleManager{
state: LifecycleStateUnmounted,
}
}
// GetLifecycleState returns the current lifecycle state
func (l *LifecycleManager) GetLifecycleState() LifecycleState {
return l.state
}
// SetLifecycleState sets the lifecycle state
func (l *LifecycleManager) SetLifecycleState(state LifecycleState) {
l.state = state
}
// Mount performs the mounting process
func (l *LifecycleManager) Mount(component LifecycleAware) {
if l.state != LifecycleStateUnmounted {
return
}
l.state = LifecycleStateMounting
component.BeforeMount()
l.state = LifecycleStateMounted
component.AfterMount()
}
// Unmount performs the unmounting process
func (l *LifecycleManager) Unmount(component LifecycleAware) {
if l.state != LifecycleStateMounted {
return
}
l.state = LifecycleStateUnmounting
component.BeforeUnmount()
l.state = LifecycleStateUnmounted
component.AfterUnmount()
}
// BaseLifecycleComponent provides a base implementation of LifecycleAware
type BaseLifecycleComponent struct {
*LifecycleManager
}
// NewBaseLifecycleComponent creates a new base lifecycle component
func NewBaseLifecycleComponent() *BaseLifecycleComponent {
return &BaseLifecycleComponent{
LifecycleManager: NewLifecycleManager(),
}
}
// BeforeMount is called before the component is mounted
func (b *BaseLifecycleComponent) BeforeMount() {
// Default implementation does nothing
}
// AfterMount is called after the component is mounted
func (b *BaseLifecycleComponent) AfterMount() {
// Default implementation does nothing
}
// BeforeUnmount is called before the component is unmounted
func (b *BaseLifecycleComponent) BeforeUnmount() {
// Default implementation does nothing
}
// AfterUnmount is called after the component is unmounted
func (b *BaseLifecycleComponent) AfterUnmount() {
// Default implementation does nothing
}
// Update is called when the component needs to update
func (b *BaseLifecycleComponent) Update() {
// Default implementation does nothing
}
```
# File: component.go
```go
// Package base provides core UI components and interfaces
package base
import (
"plants-vs-zombies/internal/core/event"
"sync"
"github.com/hajimehoshi/ebiten/v2"
)
// Handler 用于定义事件处理器接口
type Handler interface {
Handle(event.Event) bool
GetID() string
}
// BaseComponent 提供了基本的组件功能
type BaseComponent struct {
sync.RWMutex
id string
enabled bool
visible bool
focused bool
bounds *BaseBounds
parent Component
children []Component
eventTarget *event.BaseEventTarget
constraints *BaseLayoutConstraints
layout SimpleLayout
flexible bool
flexWeight float64
initialized bool
captureListeners map[event.EventType][]event.Handler
bubbleListeners map[event.EventType][]event.Handler
targetListeners map[event.EventType][]event.Handler
listenersMutex sync.RWMutex
eventListeners map[EventListenerKey][]event.Handler
}
// EventListenerKey 用于标识事件监听器
type EventListenerKey struct {
Type event.EventType
Phase event.EventPhase
UseCapture bool
}
// NewBaseComponent 创建新的基础组件
func NewBaseComponent() *BaseComponent {
b := &BaseComponent{
enabled: true,
visible: true,
bounds: NewBounds(0, 0, 0, 0),
children: make([]Component, 0),
eventTarget: event.NewBaseEventTarget(),
constraints: NewBaseLayoutConstraints(),
flexible: false,
flexWeight: 1.0,
}
return b
}
// Init 初始化组件
func (b *BaseComponent) Init() {
if b.initialized {
return
}
// 初始化事件目标
if b.eventTarget == nil {
b.eventTarget = event.NewBaseEventTarget()
}
// 初始���子组件
for _, child := range b.children {
child.Init()
}
b.initialized = true
// 发送初始化完成事件
b.EmitEvent(event.EventTypeInit)
}
// Update 更新组件状态
func (b *BaseComponent) Update() error {
if !b.enabled || !b.visible {
return nil
}
// 更新子组件
for _, child := range b.children {
if err := child.Update(); err != nil {
return err
}
}
return nil
}
// Draw 绘制组件
func (b *BaseComponent) Draw(screen *ebiten.Image) {
if !b.visible {
return
}
// 绘制子组件
for _, child := range b.children {
child.Draw(screen)
}
}
// Destroy 清理组件资源
func (b *BaseComponent) Destroy() {
// 发送销毁事件
b.EmitEvent(event.EventTypeDestroy)
// 清理子组件
for _, child := range b.children {
child.Destroy()
}
// 从父组件移除
if b.parent != nil {
b.parent.RemoveChild(b)
}
// 清理引用
b.children = nil
b.parent = nil
b.eventTarget = nil
b.layout = nil
b.initialized = false
}
// 实现 Component 接口
func (b *BaseComponent) IsEnabled() bool {
b.RLock()
defer b.RUnlock()
return b.enabled
}
func (b *BaseComponent) SetEnabled(enabled bool) {
// 先获取当前状态
var stateChanged bool
func() {
b.Lock()
defer b.Unlock()
stateChanged = b.enabled != enabled
b.enabled = enabled
}()
// 如果状态改变,在锁外发送事件
if stateChanged {
// 创建事件
var evt event.Event
if enabled {
evt = event.NewBaseEvent(event.EventTypeEnabled, true)
} else {
evt = event.NewBaseEvent(event.EventTypeDisabled, true)
}
evt.SetTarget(b.eventTarget)
// 直接调用 HandleEvent,避免通过 EmitEvent
if b.parent != nil {
b.parent.HandleEvent(evt)
} else {
b.HandleEvent(evt)
}
}
}
func (b *BaseComponent) IsVisible() bool {
b.RLock()
defer b.RUnlock()
return b.visible
}
func (b *BaseComponent) SetVisible(visible bool) {
// 先获取当前状态
var stateChanged bool
func() {
b.Lock()
defer b.Unlock()
stateChanged = b.visible != visible
b.visible = visible
}()
// 如果状态改变,在锁外发送事件
if stateChanged {
// 创建事件
var evt event.Event
if visible {
evt = event.NewBaseEvent(event.EventTypeShow, true)
} else {
evt = event.NewBaseEvent(event.EventTypeHide, true)
}
evt.SetTarget(b.eventTarget)
// 直接调用 HandleEvent,避免通过 EmitEvent
if b.parent != nil {
b.parent.HandleEvent(evt)
} else {
b.HandleEvent(evt)
}
}
}
func (b *BaseComponent) GetBounds() *BaseBounds {
return b.bounds
}
func (b *BaseComponent) SetBounds(bounds *BaseBounds) {
if b.bounds.X() != bounds.X() || b.bounds.Y() != bounds.Y() {
b.EmitEvent(event.EventTypeMove)
}
if b.bounds.Width() != bounds.Width() || b.bounds.Height() != bounds.Height() {
b.EmitEvent(event.EventTypeResize)
}
b.bounds = bounds
}
func (b *BaseComponent) Parent() Component {
return b.parent
}
func (b *BaseComponent) Children() []Component {
b.RLock()
defer b.RUnlock()
result := make([]Component, len(b.children))
copy(result, b.children)
return result
}
func (b *BaseComponent) AddChild(child Component) {
b.Lock()
defer b.Unlock()
if child == nil {
return
}
// 如果子组件已经有父组件,先从原父组件中移除
if child.Parent() != nil {
child.Parent().RemoveChild(child)
}
b.children = append(b.children, child)
child.SetParent(b)
}
// setParentNoEvent 设置父组件但不触发事件
func (b *BaseComponent) setParentNoEvent(parent Component) {
b.Lock()
b.parent = parent
b.Unlock()
}
func (b *BaseComponent) RemoveChild(child Component) {
b.Lock()
defer b.Unlock()
if child == nil {
return
}
for i, c := range b.children {
if c == child {
b.children = append(b.children[:i], b.children[i+1:]...)
child.SetParent(nil)
break
}
}
}
// 实现 event.EventTarget 接口
func (b *BaseComponent) HandleEvent(evt event.Event) bool {
if evt.IsPropagationStopped() {
return false
}
// 设置当前目标
evt.SetCurrentTarget(b.GetEventTarget())
// 根据事件相应的处理方法
switch evt.Phase() {
case event.EventPhaseCapturing:
// 捕获阶段:从根到目标的传播
b.handleCapturingPhase(evt)
case event.EventPhaseTarget:
// 目标阶段:在目标上触发事件
b.handleTargetPhase(evt)
case event.EventPhaseBubbling:
// 冒泡阶段:从目标回到根的传播
b.handleBubblingPhase(evt)
}
return !evt.IsPropagationStopped()
}
// handleCapturingPhase 处理捕获阶段
func (b *BaseComponent) handleCapturingPhase(evt event.Event) {
// 先调用当前组件的捕获阶段监听器
key := EventListenerKey{
Type: evt.Type(),
Phase: event.EventPhaseCapturing,
UseCapture: true,
}
b.callEventListeners(key, evt)
// 如果事件传播被停止,直接返回
if evt.IsPropagationStopped() {
return
}
// 如果当前组件不是目标,继续向下传播
if evt.Target() != b.GetEventTarget() {
// 获取子组件并继续传播
for _, child := range b.EventChildren() {
evt.SetPhase(event.EventPhaseCapturing)
child.HandleEvent(evt)
if evt.IsPropagationStopped() {
return
}
}
} else {
// 如果是目标,切换到目标阶段
evt.SetPhase(event.EventPhaseTarget)
b.handleTargetPhase(evt)
}
}
// handleTargetPhase 处理目标阶段
func (b *BaseComponent) handleTargetPhase(evt event.Event) {
// 在目标上调用所有监听器
key := EventListenerKey{
Type: evt.Type(),
Phase: event.EventPhaseTarget,
}
b.callEventListeners(key, evt)
// 如果事件可以冒泡且未被停止,切换到冒泡阶段
if evt.Bubbles() && !evt.IsPropagationStopped() {
evt.SetPhase(event.EventPhaseBubbling)
if parent := b.EventParent(); parent != nil {
parent.HandleEvent(evt)
}
}
}
// handleBubblingPhase 处理冒泡阶段
func (b *BaseComponent) handleBubblingPhase(evt event.Event) {
// 调用冒泡阶段的监听器
key := EventListenerKey{
Type: evt.Type(),
Phase: event.EventPhaseBubbling,
UseCapture: false,
}
b.callEventListeners(key, evt)
// 如果事件未被停止且有父组件,继续冒泡
if !evt.IsPropagationStopped() {
if parent := b.EventParent(); parent != nil {
evt.SetPhase(event.EventPhaseBubbling)
parent.HandleEvent(evt)
}
}
}
// callEventListeners 用指定类型和阶段的所有事件监听器
func (b *BaseComponent) callEventListeners(key EventListenerKey, evt event.Event) {
if handlers, ok := b.eventListeners[key]; ok {
for _, handler := range handlers {
if handler.HandleEvent(evt) {
evt.StopPropagation()
break
}
}
}
}
func (b *BaseComponent) DispatchEvent(evt event.Event) bool {
if evt == nil {
return false
}
// 如果没有设置件目标,将当前组件设为目标
if evt.Target() == nil {
evt.SetTarget(b.eventTarget)
}
// 如果没有设置事件阶段,从捕获阶段开始
if evt.Phase() == event.EventPhaseNone {
evt.SetPhase(event.EventPhaseCapturing)
}
return b.HandleEvent(evt)
}
// AddEventListener 添加事件监听器
func (b *BaseComponent) AddEventListener(eventType event.EventType, phase event.EventPhase, handler event.Handler, priority int) {
if b.eventListeners == nil {
b.eventListeners = make(map[EventListenerKey][]event.Handler)
}
key := EventListenerKey{
Type: eventType,
Phase: phase,
UseCapture: phase == event.EventPhaseCapturing,
}
b.eventListeners[key] = append(b.eventListeners[key], handler)
}
func (b *BaseComponent) RemoveEventListener(eventType event.EventType, phase event.EventPhase, handler event.Handler) {
if handler == nil {
return
}
key := EventListenerKey{
Type: eventType,
Phase: phase,
UseCapture: phase == event.EventPhaseCapturing,
}
if handlers, ok := b.eventListeners[key]; ok {
for i, h := range handlers {
if h.GetID() == handler.GetID() {
b.eventListeners[key] = append(handlers[:i], handlers[i+1:]...)
break
}
}
}
}
// 实现 Interactive 接口
func (b *BaseComponent) IsFocused() bool {
b.RLock()
defer b.RUnlock()
return b.focused
}
func (b *BaseComponent) SetFocused(focused bool) {
// 先获取当前状态
var stateChanged bool
func() {
b.Lock()
defer b.Unlock()
stateChanged = b.focused != focused
b.focused = focused
}()
// 如果状态改变,在锁外发送事件
if stateChanged {
// 创建事件
var evt event.Event
if focused {
evt = event.NewBaseEvent(event.EventTypeFocusGain, true)
} else {
evt = event.NewBaseEvent(event.EventTypeFocusLoss, true)
}
evt.SetTarget(b.eventTarget)
// 直接调用 HandleEvent,避免通过 DispatchEvent
if b.parent != nil {
b.parent.HandleEvent(evt)
} else {
// 如果没有父组件,直接处理事件
func() {
b.listenersMutex.RLock()
defer b.listenersMutex.RUnlock()
var handlers []event.Handler
if focused {
handlers = b.targetListeners[event.EventTypeFocusGain]
} else {
handlers = b.targetListeners[event.EventTypeFocusLoss]
}
if len(handlers) > 0 {
// 复制处理器列表,避免在持有锁时调用处理器
handlersCopy := make([]event.Handler, len(handlers))
copy(handlersCopy, handlers)
// 在锁外调用处理器
for _, handler := range handlersCopy {
if handler != nil {
handler.HandleEvent(evt)
}
}
}
}()
}
}
}
// 实现 Container 接口
func (b *BaseComponent) Layout() {
if b.layout != nil && len(b.children) > 0 {
// 如果有布局管理器且有子组件,则进行布局
b.layout.Layout(b)
}
}
func (b *BaseComponent) SetLayout(layout SimpleLayout) {
b.layout = layout
}
func (b *BaseComponent) AddComponent(component Component) {
b.AddChild(component)
}
func (b *BaseComponent) RemoveComponent(component Component) {
b.RemoveChild(component)
}
func (b *BaseComponent) GetComponents() []Component {
return b.Children()
}
func (b *BaseComponent) Clear() {
b.Lock()
defer b.Unlock()
for _, child := range b.children {
if child != nil {
child.Destroy()
}
}
b.children = make([]Component, 0)
b.EmitEvent(event.EventTypeChildrenCleared)
}
func (b *BaseComponent) GetPadding() *Margins {
return &Margins{} // 默认返回空边距
}
func (b *BaseComponent) SetPadding(*Margins) {
// 基础组件不实现边距
}
func (b *BaseComponent) GetMargin() *Margins {
return &Margins{} // 默认返回空边距
}
func (b *BaseComponent) SetMargin(*Margins) {
// 基础组件不实现边距
}
// SetParent 设置父组件
func (b *BaseComponent) SetParent(parent Component) {
var changed bool
func() {
b.Lock()
defer b.Unlock()
if b.parent != parent {
b.parent = parent
changed = true
}
}()
// 如果父组件发生变化,在锁外发送事件
if changed {
evt := event.NewBaseEvent(event.EventTypeParentChanged, true)
evt.SetTarget(b.eventTarget)
// 直接处理事件,避免使用 DispatchEvent
var handlers []event.Handler
func() {
b.listenersMutex.RLock()
defer b.listenersMutex.RUnlock()
if h := b.targetListeners[event.EventTypeParentChanged]; len(h) > 0 {
handlers = make([]event.Handler, len(h))
copy(handlers, h)
}
}()
// 在锁外调用处理器
for _, handler := range handlers {
if handler != nil {
handler.HandleEvent(evt)
}
}
}
}
// SetConstraints 设置布局约束
func (b *BaseComponent) SetConstraints(constraints *BaseLayoutConstraints) {
b.constraints = constraints
}
// GetConstraints 获取布局约束
func (b *BaseComponent) GetConstraints() *BaseLayoutConstraints {
return b.constraints
}
// SetFlexible 设置是否可伸缩
func (b *BaseComponent) SetFlexible(flexible bool) {
b.flexible = flexible
}
// SetFlexWeight 设置伸缩权重
func (b *BaseComponent) SetFlexWeight(weight float64) {
b.flexWeight = weight
}
// EmitEvent 发送事件
func (b *BaseComponent) EmitEvent(eventType event.EventType) {
if b == nil {
return
}
// 创建事件
evt := event.NewBaseEvent(eventType, true)
evt.SetTarget(b.eventTarget)
// 获取处理器的副本,避免长时间持有锁
var handlers []event.Handler
func() {
b.listenersMutex.RLock()
defer b.listenersMutex.RUnlock()
if h := b.targetListeners[eventType]; len(h) > 0 {
handlers = make([]event.Handler, len(h))
copy(handlers, h)
}
}()
// 在锁外调用处理器
for _, handler := range handlers {
if handler != nil {
handler.HandleEvent(evt)
}
}
}
// OnEvent 添加事件处理器
func (b *BaseComponent) OnEvent(eventType event.EventType, handler func(event.Event) bool) {
b.AddEventListener(eventType, event.EventPhaseTarget, event.NewHandler(handler, event.EventPhaseTarget), 0)
}
// OffEvent 移除事件处理器
func (b *BaseComponent) OffEvent(eventType event.EventType, handler func(event.Event) bool) {
b.RemoveEventListener(eventType, event.EventPhaseTarget, event.NewHandler(handler, event.EventPhaseTarget))
}
// GetLayoutConstraints 获取布局约束
func (b *BaseComponent) GetLayoutConstraints() *BaseLayoutConstraints {
return b.constraints
}
// SetLayoutConstraints 设置布局约束
func (b *BaseComponent) SetLayoutConstraints(constraints *BaseLayoutConstraints) {
b.constraints = constraints
// 更新布局
if b.layout != nil {
b.Layout()
}
}
// GetEventTarget 返回组件的事件目标
func (c *BaseComponent) GetEventTarget() event.EventTarget {
if c == nil {
return nil
}
return c.eventTarget
}
// EventChildren 返回子组件的事件目标
func (b *BaseComponent) EventChildren() []event.EventTarget {
if b == nil {
return nil
}
b.RLock()
defer b.RUnlock()
if b.children == nil {
return nil
}
children := make([]event.EventTarget, 0, len(b.children))
for _, child := range b.children {
if child == nil {
continue
}
if eventTarget, ok := child.(event.EventTarget); ok && eventTarget != nil {
children = append(children, eventTarget)
}
}
return children
}
// EventParent 返回父组件的事件目标
func (b *BaseComponent) EventParent() event.EventTarget {
if b == nil {
return nil
}
b.RLock()
defer b.RUnlock()
if b.parent == nil {
return nil
}
if eventTarget, ok := b.parent.(event.EventTarget); ok && eventTarget != nil {
return eventTarget
}
return nil
}
// GetLayout 获取布局管理器
func (b *BaseComponent) GetLayout() SimpleLayout {
return b.layout
}
// Equal 比较两个事件目标是否相等
func (b *BaseComponent) Equal(other event.EventTarget) bool {
if other == nil {
return false
}
// 尝试将other转换为*BaseComponent
if otherComponent, ok := other.(*BaseComponent); ok {
return b == otherComponent
}
return false
}
// TargetID 返回事件目标的唯一标识符
func (b *BaseComponent) TargetID() string {
return b.id
}
// AddEventChild 添加事件子组件
func (b *BaseComponent) AddEventChild(child event.EventTarget) {
if b == nil || child == nil {
return
}
b.Lock()
defer b.Unlock()
// 如果子组件也是Component,则通过AddChild添加
if component, ok := child.(Component); ok {
b.AddChild(component)
return
}
// 保eventTarget已初始化
if b.eventTarget == nil {
b.eventTarget = event.NewBaseEventTarget()
}
b.eventTarget.AddEventChild(child)
}
// RemoveEventChild 移除事件子组件
func (b *BaseComponent) RemoveEventChild(child event.EventTarget) {
if b == nil || child == nil {
return
}
b.Lock()
defer b.Unlock()
// 如果子组件也是Component,则通过RemoveChild移除
if component, ok := child.(Component); ok {
b.RemoveChild(component)
return
}
// 确保eventTarget已初始化
if b.eventTarget != nil {
b.eventTarget.RemoveEventChild(child)
}
}
// SetEventParent 设置事件父组件
func (b *BaseComponent) SetEventParent(parent event.EventTarget) {
if b == nil {
return
}
// 如果父组件也是Component,则通过SetParent设置
if component, ok := parent.(Component); ok {
b.SetParent(component)
return
}
// 确保eventTarget已初始化
var target event.EventTarget
func() {
b.Lock()
defer b.Unlock()
if b.eventTarget == nil {
b.eventTarget = event.NewBaseEventTarget()
}
target = b.eventTarget
}()
// 在锁外处理事件目标关系
if target != nil {
target.SetEventParent(parent)
}
}
```
# File: container_test.go
```go
package base
import (
"plants-vs-zombies/internal/core/event"
"reflect"
"sync"
"testing"
)
func TestBaseContainer(t *testing.T) {
t.Run("测试事件处理", func(t *testing.T) {
container := NewBaseContainer()
child1 := NewBaseComponent()
child2 := NewBaseComponent()
container.AddComponent(child1)
container.AddComponent(child2)
eventHandled := false
handler := NewEventHandler(func(evt event.Event) bool {
if evt.Target() == child1.GetEventTarget() && evt.CurrentTarget() == child1.GetEventTarget() {
eventHandled = true
}
return true
})
child1.AddEventListener(event.EventTypeClick, event.EventPhaseTarget, handler, 0)
evt := event.NewBaseEvent(event.EventTypeClick, true)
evt.SetTarget(child1.GetEventTarget())
evt.SetPhase(event.EventPhaseCapturing)
evt.SetCurrentTarget(child1.GetEventTarget())
container.HandleEvent(evt)
if !eventHandled {
t.Error("事件未被正确处理")
}
})
t.Run("测试事件冒泡", func(t *testing.T) {
container := NewBaseContainer()
child := NewBaseComponent()
container.AddComponent(child)
bubbleHandled := false
handler := NewEventHandler(func(evt event.Event) bool {
if evt.Phase() == event.EventPhaseBubbling && evt.CurrentTarget() == container.GetEventTarget() {
bubbleHandled = true
}
return true
})
container.AddEventListener(event.EventTypeClick, event.EventPhaseBubbling, handler, 0)
evt := event.NewBaseEvent(event.EventTypeClick, true)
evt.SetTarget(child.GetEventTarget())
evt.SetPhase(event.EventPhaseCapturing)
evt.SetCurrentTarget(container.GetEventTarget())
container.DispatchEvent(evt)
if !bubbleHandled {
t.Error("事件冒泡未被正确处理")
}
})
t.Run("测试事件传播顺序", func(t *testing.T) {
container := NewBaseContainer()
child := NewBaseComponent()
container.AddComponent(child)
var eventOrder []string
var mu sync.Mutex
captureHandler := NewEventHandler(func(evt event.Event) bool {
if evt.Phase() == event.EventPhaseCapturing && evt.CurrentTarget() == container.GetEventTarget() {
mu.Lock()
eventOrder = append(eventOrder, "container-capture")
mu.Unlock()
}
return false
})
container.AddEventListener(event.EventTypeClick, event.EventPhaseCapturing, captureHandler, 0)
targetHandler := NewEventHandler(func(evt event.Event) bool {
if evt.Phase() == event.EventPhaseTarget && evt.Target() == child.GetEventTarget() && evt.CurrentTarget() == child.GetEventTarget() {
mu.Lock()
eventOrder = append(eventOrder, "child-target")
mu.Unlock()
}
return false
})
child.AddEventListener(event.EventTypeClick, event.EventPhaseTarget, targetHandler, 0)
bubbleHandler := NewEventHandler(func(evt event.Event) bool {
if evt.Phase() == event.EventPhaseBubbling && evt.CurrentTarget() == container.GetEventTarget() {
mu.Lock()
eventOrder = append(eventOrder, "container-bubble")
mu.Unlock()
}
return false
})
container.AddEventListener(event.EventTypeClick, event.EventPhaseBubbling, bubbleHandler, 0)
evt := event.NewBaseEvent(event.EventTypeClick, true)
evt.SetTarget(child.GetEventTarget())
evt.SetPhase(event.EventPhaseCapturing)
evt.SetCurrentTarget(container.GetEventTarget())
container.DispatchEvent(evt)
expected := []string{"container-capture", "child-target", "container-bubble"}
if !reflect.DeepEqual(eventOrder, expected) {
t.Errorf("事件传播顺序错误,期望 %v,得到 %v", expected, eventOrder)
}
})
}
func TestContainerLayout(t *testing.T) {
container := NewBaseContainer()
container.SetBounds(NewBounds(0, 0, 100, 100))
// 创建边距对象
padding := NewMargins(5, 5, 5, 5)
margin := NewMargins(10, 10, 10, 10)
// 设置边距
container.SetPadding(padding) // 直接传递,因为 NewMargins 已经返回指针
container.SetMargin(margin) // 直接传递,因为 NewMargins 已经返回指针
// 验证边距计算
if container.GetPadding().Left != 5 {
t.Errorf("内边距设置错误,期望 5,得到 %v", container.GetPadding().Left)
}
if container.GetMargin().Left != 10 {
t.Errorf("外边距设置错误,期望 10,得到 %v", container.GetMargin().Left)
}
}
func TestContainerChildren(t *testing.T) {
container := NewBaseContainer()
child := NewBaseComponent()
// 测试添加子组件
container.AddComponent(child)
if len(container.GetComponents()) != 1 {
t.Error("添加子组件失败")
}
// 测试移除子组件
container.RemoveComponent(child)
if len(container.GetComponents()) != 0 {
t.Error("移除子组件失败")
}
// 测试清除所有子组件
container.AddComponent(child)
container.Clear()
if len(container.GetComponents()) != 0 {
t.Error("清除子组件失败")
}
}
```
# File: pool.go
```go
package base
import (
"sync"
)
// ComponentPool 组件对象池
type ComponentPool struct {
pool sync.Pool
}
// NewComponentPool 创建新的组件池
func NewComponentPool() *ComponentPool {
return &ComponentPool{
pool: sync.Pool{
New: func() interface{} {
return NewBaseComponent()
},
},
}
}
// Get 从池中获取组件
func (p *ComponentPool) Get() Component {
comp := p.pool.Get().(Component)
comp.Init() // 重新初始化组件
return comp
}
// Put 将组件放回池中
func (p *ComponentPool) Put(comp Component) {
if comp == nil {
return
}
// 清理组件状态
comp.Destroy()
// 重置基本属性
if baseComp, ok := comp.(*BaseComponent); ok {
baseComp.enabled = true
baseComp.visible = true
baseComp.focused = false
baseComp.initialized = false
baseComp.children = make([]Component, 0)
baseComp.parent = nil
baseComp.bounds = NewBounds(0, 0, 0, 0)
baseComp.constraints = NewBaseLayoutConstraints()
baseComp.layout = nil
baseComp.flexible = false
baseComp.flexWeight = 1.0
}
p.pool.Put(comp)
}
// GlobalComponentPool 全局组件池实例
var GlobalComponentPool = NewComponentPool()
// 组件池工具函数
// GetComponent 从全局池获取组件
func GetComponent() Component {
return GlobalComponentPool.Get()
}
// PutComponent 将组件放回全局池
func PutComponent(comp Component) {
GlobalComponentPool.Put(comp)
}
// GetComponents 批量获取组件
func GetComponents(count int) []Component {
components := make([]Component, count)
for i := 0; i < count; i++ {
components[i] = GetComponent()
}
return components
}
// PutComponents 批量回收组件
func PutComponents(components []Component) {
for _, comp := range components {
PutComponent(comp)
}
}
```
# File: layout_test.go
```go
package base
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLayoutConstraints(t *testing.T) {
t.Run("Validation", func(t *testing.T) {
constraints := NewBaseLayoutConstraints()
// 测试默认值
assert.Equal(t, float64(0), constraints.MinWidth())
assert.Equal(t, float64(-1), constraints.MaxWidth())
assert.Equal(t, float64(0), constraints.MinHeight())
assert.Equal(t, float64(-1), constraints.MaxHeight())
// 测试设置值
constraints.SetMinWidth(100)
constraints.SetMaxWidth(200)
constraints.SetMinHeight(150)
constraints.SetMaxHeight(300)
assert.Equal(t, float64(100), constraints.MinWidth())
assert.Equal(t, float64(200), constraints.MaxWidth())
assert.Equal(t, float64(150), constraints.MinHeight())
assert.Equal(t, float64(300), constraints.MaxHeight())
// 测试验证
assert.NoError(t, constraints.Validate())
// 测试无效值
constraints.SetMinWidth(-1)
assert.Error(t, constraints.Validate())
constraints.SetMinWidth(300)
assert.Error(t, constraints.Validate())
})
}
func TestSimpleFlowLayout(t *testing.T) {
t.Run("HorizontalLayout", func(t *testing.T) {
container := NewBaseComponent()
container.SetBounds(NewBounds(0, 0, 300, 100))
// 添加三个子组件
child1 := NewBaseComponent()
child1.SetBounds(NewBounds(0, 0, 80, 50))
container.AddChild(child1)
child2 := NewBaseComponent()
child2.SetBounds(NewBounds(0, 0, 80, 60))
container.AddChild(child2)
child3 := NewBaseComponent()
child3.SetBounds(NewBounds(0, 0, 80, 40))
container.AddChild(child3)
// 创建水平流式布局
layout := NewSimpleFlowLayout(10, Horizontal)
layout.Layout(container)
// 验证布局结果
assert.Equal(t, float64(0), child1.GetBounds().X())
assert.Equal(t, float64(90), child2.GetBounds().X())
assert.Equal(t, float64(180), child3.GetBounds().X())
})
t.Run("VerticalLayout", func(t *testing.T) {
container := NewBaseComponent()
container.SetBounds(NewBounds(0, 0, 100, 300))
// 添加三个子组件
child1 := NewBaseComponent()
child1.SetBounds(NewBounds(0, 0, 80, 50))
container.AddChild(child1)
child2 := NewBaseComponent()
child2.SetBounds(NewBounds(0, 0, 80, 60))
container.AddChild(child2)
child3 := NewBaseComponent()
child3.SetBounds(NewBounds(0, 0, 80, 40))
container.AddChild(child3)
// 创建垂直流式布局
layout := NewSimpleFlowLayout(10, Vertical)
layout.Layout(container)
// 验证布局结果
assert.Equal(t, float64(0), child1.GetBounds().Y())
assert.Equal(t, float64(60), child2.GetBounds().Y())
assert.Equal(t, float64(130), child3.GetBounds().Y())
})
}
func TestSimpleGridLayout(t *testing.T) {
t.Run("2x2Grid", func(t *testing.T) {
container := NewBaseComponent()
container.SetBounds(NewBounds(0, 0, 200, 200))
// 添加四个子组件
for i := 0; i < 4; i++ {
child := NewBaseComponent()
child.SetBounds(NewBounds(0, 0, 50, 50))
container.AddChild(child)
}
// 创建2x2网格布局
layout := NewSimpleGridLayout(2, 10)
layout.Layout(container)
children := container.Children()
// 验证第一行
assert.Equal(t, float64(0), children[0].GetBounds().X())
assert.Equal(t, float64(0), children[0].GetBounds().Y())
assert.Equal(t, float64(105), children[1].GetBounds().X())
assert.Equal(t, float64(0), children[1].GetBounds().Y())
// 验证第二行
assert.Equal(t, float64(0), children[2].GetBounds().X())
assert.Equal(t, float64(105), children[2].GetBounds().Y())
assert.Equal(t, float64(105), children[3].GetBounds().X())
assert.Equal(t, float64(105), children[3].GetBounds().Y())
})
t.Run("EmptyContainer", func(t *testing.T) {
container := NewBaseComponent()
container.SetBounds(NewBounds(0, 0, 200, 200))
layout := NewSimpleGridLayout(2, 10)
layout.Layout(container)
// 验证空容器不会导致错误
assert.Empty(t, container.Children())
})
t.Run("InvalidColumns", func(t *testing.T) {
container := NewBaseComponent()
container.SetBounds(NewBounds(0, 0, 200, 200))
child := NewBaseComponent()
container.AddChild(child)
layout := NewSimpleGridLayout(0, 10)
layout.Layout(container)
// 验证无效列数不会导致错误
assert.NotPanics(t, func() {
layout.Layout(container)
})
})
}
func BenchmarkLayouts(b *testing.B) {
b.Run("FlowLayout", func(b *testing.B) {
container := NewBaseComponent()
container.SetBounds(NewBounds(0, 0, 1000, 1000))
// 添加100个子组件
for i := 0; i < 100; i++ {
child := NewBaseComponent()
child.SetBounds(NewBounds(0, 0, 50, 50))
container.AddChild(child)
}
layout := NewSimpleFlowLayout(10, Horizontal)
b.ResetTimer()
for i := 0; i < b.N; i++ {
layout.Layout(container)
}
})
b.Run("GridLayout", func(b *testing.B) {
container := NewBaseComponent()
container.SetBounds(NewBounds(0, 0, 1000, 1000))
// 添加100个子组件
for i := 0; i < 100; i++ {
child := NewBaseComponent()
child.SetBounds(NewBounds(0, 0, 50, 50))
container.AddChild(child)
}
layout := NewSimpleGridLayout(10, 10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
layout.Layout(container)
}
})
}
```
# File: layout.go
```go
// Package base provides core UI components and interfaces
package base
import (
"fmt"
)
// Orientation represents component orientation
type Orientation int
const (
// Horizontal orientation arranges components from left to right
Horizontal Orientation = iota
// Vertical orientation arranges components from top to bottom
Vertical
)
// Alignment represents component alignment options
type Alignment int
const (
AlignStart Alignment = iota // Left or top alignment
AlignCenter // Center alignment
AlignEnd // Right or bottom alignment
AlignStretch // Stretch to fill available space
)
// BaseLayoutConstraints defines layout constraints for a component
type BaseLayoutConstraints struct {
minWidth float64
maxWidth float64
minHeight float64
maxHeight float64
orientation Orientation
alignment Alignment
margin Margins
padding Margins
}
// NewBaseLayoutConstraints creates a new BaseLayoutConstraints instance
func NewBaseLayoutConstraints() *BaseLayoutConstraints {
return &BaseLayoutConstraints{
minWidth: 0,
maxWidth: -1, // -1 means no limit
minHeight: 0,
maxHeight: -1,
orientation: Horizontal,
alignment: AlignStart,
margin: Margins{},
padding: Margins{},
}
}
// Validate validates the layout constraints
func (c *BaseLayoutConstraints) Validate() error {
if c.minWidth < 0 {
return fmt.Errorf("minimum width cannot be negative")
}
if c.maxWidth >= 0 && c.maxWidth < c.minWidth {
return fmt.Errorf("maximum width cannot be less than minimum width")
}
if c.minHeight < 0 {
return fmt.Errorf("minimum height cannot be negative")
}
if c.maxHeight >= 0 && c.maxHeight < c.minHeight {
return fmt.Errorf("maximum height cannot be less than minimum height")
}
return nil
}
// Getters and setters
func (c *BaseLayoutConstraints) MinWidth() float64 { return c.minWidth }
func (c *BaseLayoutConstraints) SetMinWidth(width float64) { c.minWidth = width }
func (c *BaseLayoutConstraints) MaxWidth() float64 { return c.maxWidth }
func (c *BaseLayoutConstraints) SetMaxWidth(width float64) { c.maxWidth = width }
func (c *BaseLayoutConstraints) MinHeight() float64 { return c.minHeight }
func (c *BaseLayoutConstraints) SetMinHeight(height float64) { c.minHeight = height }
func (c *BaseLayoutConstraints) MaxHeight() float64 { return c.maxHeight }
func (c *BaseLayoutConstraints) SetMaxHeight(height float64) { c.maxHeight = height }
func (c *BaseLayoutConstraints) Orientation() Orientation { return c.orientation }
func (c *BaseLayoutConstraints) SetOrientation(orientation Orientation) { c.orientation = orientation }
func (c *BaseLayoutConstraints) Alignment() Alignment { return c.alignment }
func (c *BaseLayoutConstraints) SetAlignment(alignment Alignment) { c.alignment = alignment }
func (c *BaseLayoutConstraints) Margin() Margins { return c.margin }
func (c *BaseLayoutConstraints) SetMargin(margin Margins) { c.margin = margin }
func (c *BaseLayoutConstraints) Padding() Margins { return c.padding }
func (c *BaseLayoutConstraints) SetPadding(padding Margins) { c.padding = padding }
// SimpleFlowLayout 实现简单的流式布局
type SimpleFlowLayout struct {
spacing float64
direction Orientation
}
// NewSimpleFlowLayout 创建新的流式布局
func NewSimpleFlowLayout(spacing float64, direction Orientation) *SimpleFlowLayout {
return &SimpleFlowLayout{
spacing: spacing,
direction: direction,
}
}
// Layout 实现流式布局
func (l *SimpleFlowLayout) Layout(container Container) {
if container == nil {
return
}
components := container.GetComponents()
if len(components) == 0 {
return
}
bounds := container.GetBounds()
if bounds == nil {
return
}
// 获取容器的可用空间
x := bounds.X()
y := bounds.Y()
maxWidth := bounds.Width()
// 当前行的最大高度
var lineHeight float64
startX := x
// 布局每个组件
for _, comp := range components {
if !comp.IsVisible() {
continue
}
compBounds := comp.GetBounds()
if compBounds == nil {
continue
}
if l.direction == Horizontal {
// 水平布局
if x > startX && x+compBounds.Width() > bounds.X()+maxWidth {
x = startX
y += lineHeight + l.spacing
lineHeight = 0
}
// 设置组件位置
compBounds.SetX(x)
compBounds.SetY(y)
comp.SetBounds(compBounds)
// 更新位置和行高
x += compBounds.Width() + l.spacing
if compBounds.Height() > lineHeight {
lineHeight = compBounds.Height()
}
} else {
// 垂直布局
compBounds.SetX(x)
compBounds.SetY(y)
comp.SetBounds(compBounds)
// 更新位置
y += compBounds.Height() + l.spacing
}
}
}
// SimpleGridLayout 实现简单的网格布局
type SimpleGridLayout struct {
columns int
spacing float64
}
// NewSimpleGridLayout 创建新的网格布局
func NewSimpleGridLayout(columns int, spacing float64) *SimpleGridLayout {
return &SimpleGridLayout{
columns: columns,
spacing: spacing,
}
}
// Layout 实现网格布局
func (l *SimpleGridLayout) Layout(container Container) {
if container == nil || l.columns <= 0 {
return
}
components := container.GetComponents()
if len(components) == 0 {
return
}
bounds := container.GetBounds()
if bounds == nil {
return
}
// 计算单元格大小
cellWidth := (bounds.Width() - float64(l.columns-1)*l.spacing) / float64(l.columns)
cellHeight := cellWidth // 默认使用正方形单元格
// 布局每个组件
row := 0
col := 0
x := bounds.X()
y := bounds.Y()
for _, comp := range components {
if !comp.IsVisible() {
continue
}
compBounds := comp.GetBounds()
if compBounds == nil {
continue
}
// 设置组件位置和大小
compBounds.SetX(x + float64(col)*(cellWidth+l.spacing))
compBounds.SetY(y + float64(row)*(cellHeight+l.spacing))
compBounds.SetWidth(cellWidth)
compBounds.SetHeight(cellHeight)
comp.SetBounds(compBounds)
// 移动到下一个位置
col++
if col >= l.columns {
col = 0
row++
}
}
}
```
# File: interactive.go
```go
package base
import (
"plants-vs-zombies/internal/core/event"
)
// InteractiveComponent 提供可交互组件的基本实现
type InteractiveComponent struct {
*BaseComponent
hover bool
pressed bool
focused bool
}
// NewInteractiveComponent 创建新的可交互组件
func NewInteractiveComponent() *InteractiveComponent {
i := &InteractiveComponent{
BaseComponent: NewBaseComponent(),
hover: false,
pressed: false,
focused: false,
}
return i
}
// GetEventTarget 获取事件目标
func (i *InteractiveComponent) GetEventTarget() event.EventTarget {
return i
}
// EventParent 获取事件父组件
func (i *InteractiveComponent) EventParent() event.EventTarget {
if i.parent == nil {
return nil
}
if target, ok := i.parent.(event.EventTarget); ok {
return target
}
return nil
}
// EventChildren 获取子组件列表
func (i *InteractiveComponent) EventChildren() []event.EventTarget {
var children []event.EventTarget
for _, child := range i.children {
if target, ok := child.(event.EventTarget); ok {
children = append(children, target)
}
}
return children
}
// AddEventChild 添加子组件
func (i *InteractiveComponent) AddEventChild(child event.EventTarget) {
if comp, ok := child.(Component); ok {
i.AddChild(comp)
}
}
// RemoveEventChild 移除子组件
func (i *InteractiveComponent) RemoveEventChild(child event.EventTarget) {
if comp, ok := child.(Component); ok {
i.RemoveChild(comp)
}
}
// AddEventListener 添加事件监听器
func (i *InteractiveComponent) AddEventListener(eventType event.EventType, phase event.EventPhase, handler event.Handler, priority int) {
i.BaseComponent.AddEventListener(eventType, phase, handler, priority)
}
// RemoveEventListener 移除事件监听器
func (i *InteractiveComponent) RemoveEventListener(eventType event.EventType, phase event.EventPhase, handler event.Handler) {
i.BaseComponent.RemoveEventListener(eventType, phase, handler)
}
// HandleEvent 处理事件
func (i *InteractiveComponent) HandleEvent(evt event.Event) bool {
if !i.IsEnabled() || !i.IsVisible() {
return false
}
// 设置当前目标
evt.SetCurrentTarget(i)
// 处理鼠标事件
if mouseEvt, ok := evt.(*event.MouseEvent); ok {
// 检查鼠标是否在组件范围内
inBounds := i.GetBounds().Contains(mouseEvt.GetX(), mouseEvt.GetY())
switch evt.Type() {
case event.EventTypeMouseMove:
// 处理鼠标进入/离开
if inBounds && !i.hover {
i.hover = true
enterEvt := event.NewBaseEvent(event.EventTypeMouseEnter, true)
enterEvt.SetTarget(i)
enterEvt.SetPhase(event.EventPhaseTarget)
i.DispatchEvent(enterEvt)
i.BaseComponent.Layout()
} else if !inBounds && i.hover {
i.hover = false
leaveEvt := event.NewBaseEvent(event.EventTypeMouseLeave, true)
leaveEvt.SetTarget(i)
leaveEvt.SetPhase(event.EventPhaseTarget)
i.DispatchEvent(leaveEvt)
i.BaseComponent.Layout()
}
case event.EventTypeMouseButtonDown:
if inBounds {
i.pressed = true
// 设置焦点
if !i.focused {
i.SetFocused(true)
}
i.BaseComponent.Layout()
}
case event.EventTypeMouseButtonUp:
wasPressed := i.pressed
i.pressed = false
if inBounds {
i.BaseComponent.Layout()
// 如果之前是按下状态,触发点击事件
if wasPressed {
clickEvt := event.NewBaseEvent(event.EventTypeClick, true)
clickEvt.SetTarget(i)
clickEvt.SetPhase(event.EventPhaseTarget)
i.DispatchEvent(clickEvt)
}
}
}
}
// 根据事件阶段处理
switch evt.Phase() {
case event.EventPhaseCapturing:
// 捕获阶段:从根到目标的传播
return i.BaseComponent.HandleEvent(evt)
case event.EventPhaseTarget:
// 目标阶段:在目标上触发事件
key := EventListenerKey{
Type: evt.Type(),
Phase: event.EventPhaseTarget,
}
i.callEventListeners(key, evt)
// 如果事件可以冒泡且有父组件,切换到冒泡阶段并继续传播
if evt.Bubbles() && !evt.IsPropagationStopped() {
evt.SetPhase(event.EventPhaseBubbling)
if parent := i.EventParent(); parent != nil {
return parent.HandleEvent(evt)
}
}
return !evt.IsPropagationStopped()
case event.EventPhaseBubbling:
// 冒泡阶段:处理当前组件的监听器
key := EventListenerKey{
Type: evt.Type(),
Phase: event.EventPhaseTarget,
}
i.callEventListeners(key, evt)
// 继续向上冒泡
if !evt.IsPropagationStopped() {
if parent := i.EventParent(); parent != nil {
return parent.HandleEvent(evt)
}
}
return !evt.IsPropagationStopped()
default:
// 如果没有指定阶段,从目标阶段开始
evt.SetPhase(event.EventPhaseTarget)
return i.HandleEvent(evt)
}
}
// SetFocused 设置焦点状态
func (i *InteractiveComponent) SetFocused(focused bool) {
if i.focused == focused {
return
}
i.focused = focused
// 创建焦点事件
var evt event.Event
if focused {
evt = event.NewBaseEvent(event.EventTypeFocusGain, true)
} else {
evt = event.NewBaseEvent(event.EventTypeFocusLoss, true)
}
// 设置事件目标
evt.SetTarget(i)
evt.SetPhase(event.EventPhaseTarget)
// 分发事件
i.DispatchEvent(evt)
// 更新布局
i.BaseComponent.Layout()
}
// IsFocused 获取焦点状态
func (i *InteractiveComponent) IsFocused() bool {
return i.focused
}
// IsHovered 获取悬停状态
func (i *InteractiveComponent) IsHovered() bool {
return i.hover
}
// IsPressed 获取按下状态
func (i *InteractiveComponent) IsPressed() bool {
return i.pressed
}
// SetEnabled 设置是否启用
func (i *InteractiveComponent) SetEnabled(enabled bool) {
if i.BaseComponent.IsEnabled() == enabled {
return
}
i.BaseComponent.SetEnabled(enabled)
if !enabled {
i.hover = false
i.pressed = false
if i.focused {
i.SetFocused(false)
}
}
i.BaseComponent.Layout()
}
// SetVisible 设置是否可见
func (i *InteractiveComponent) SetVisible(visible bool) {
if i.BaseComponent.IsVisible() == visible {
return
}
i.BaseComponent.SetVisible(visible)
if !visible {
i.hover = false
i.pressed = false
if i.focused {
i.SetFocused(false)
}
}
i.BaseComponent.Layout()
}
// Update 更新组件状态
func (c *InteractiveComponent) Update() error {
if err := c.BaseComponent.Update(); err != nil {
return err
}
// 更新悬停状态
if c.IsEnabled() && c.IsVisible() {
// TODO: 获取当前鼠标位置并更新悬停状态
}
return nil
}
```
# File: bounds.go
```go
package base
import (
"fmt"
)
// BaseBounds 定义组件的位置和大小
type BaseBounds struct {
x float64
y float64
width float64
height float64
}
// NewBounds 创建新的边界对象
func NewBounds(x, y, width, height float64) *BaseBounds {
return &BaseBounds{
x: x,
y: y,
width: width,
height: height,
}
}
// X 返回 x 坐标
func (b *BaseBounds) X() float64 { return b.x }
// Y 返回 y 坐标
func (b *BaseBounds) Y() float64 { return b.y }
// Width 返回宽度
func (b *BaseBounds) Width() float64 { return b.width }
// Height 返回高度
func (b *BaseBounds) Height() float64 { return b.height }
// SetX 设置 x 坐标
func (b *BaseBounds) SetX(x float64) { b.x = x }
// SetY 设置 y 坐标
func (b *BaseBounds) SetY(y float64) { b.y = y }
// SetWidth 设置宽度
func (b *BaseBounds) SetWidth(width float64) { b.width = width }
// SetHeight 设置高度
func (b *BaseBounds) SetHeight(height float64) { b.height = height }
// Contains 检查点是否在边界内
func (b *BaseBounds) Contains(x, y float64) bool {
return x >= b.x && x <= b.x+b.width &&
y >= b.y && y <= b.y+b.height
}
// Intersects 检查是否与另一个边界相交
func (b *BaseBounds) Intersects(other *BaseBounds) bool {
if other == nil {
return false
}
// 两个矩形不相交的条件:
// 1. 一个在另一个的左边
// 2. 一个在另一个的右边
// 3. 一个在另一个的上边
// 4. 一个在另一个的下边
return !(b.x >= other.x+other.width || // 当前矩形在另一个的右边
b.x+b.width <= other.x || // 当前矩形在另一个的左边
b.y >= other.y+other.height || // 当前矩形在另一个的下边
b.y+b.height <= other.y) // 当前矩形在另一个的上边
}
// Clone 创建边界的副本
func (b *BaseBounds) Clone() *BaseBounds {
return NewBounds(b.x, b.y, b.width, b.height)
}
// String 返回边界的字符串表示
func (b *BaseBounds) String() string {
return fmt.Sprintf("Bounds(x:%.1f, y:%.1f, w:%.1f, h:%.1f)",
b.x, b.y, b.width, b.height)
}
```
# File: interfaces.go
```go
package base
import (
"plants-vs-zombies/internal/core/event"
"github.com/hajimehoshi/ebiten/v2"
)
// Component 定义了基本的UI组件接口
type Component interface {
// 基本属性
IsEnabled() bool
SetEnabled(bool)
IsVisible() bool
SetVisible(bool)
IsFocused() bool
SetFocused(bool)
// 布局相关
GetBounds() *BaseBounds
SetBounds(*BaseBounds)
GetLayoutConstraints() *BaseLayoutConstraints
SetLayoutConstraints(*BaseLayoutConstraints)
Layout()
SetLayout(SimpleLayout)
// 组件树操作
Parent() Component
SetParent(Component)
Children() []Component
AddChild(Component)
RemoveChild(Component)
AddComponent(Component)
RemoveComponent(Component)
GetComponents() []Component
// 生命周期
Init()
Update() error
Draw(*ebiten.Image)
Destroy()
// 事件系统 - 修改为与 EventTarget 接口一致
GetEventTarget() event.EventTarget
HandleEvent(event.Event) bool
DispatchEvent(event.Event) bool
// 修改事件监听器方法签名
AddEventListener(eventType event.EventType, phase event.EventPhase, handler event.Handler, priority int)
RemoveEventListener(eventType event.EventType, phase event.EventPhase, handler event.Handler)
EmitEvent(eventType event.EventType)
// 保留便捷方法
OnEvent(eventType event.EventType, handler func(event.Event) bool)
OffEvent(eventType event.EventType, handler func(event.Event) bool)
}
// Container 定义了容器组件的接口
type Container interface {
Component
Clear()
GetPadding() *Margins
SetPadding(*Margins)
GetMargin() *Margins
SetMargin(*Margins)
}
// Interactive 定义了可交互组件的接口
type Interactive interface {
Component
IsHovered() bool
SetHovered(bool)
IsPressed() bool
SetPressed(bool)
}
// SimpleLayout 定义了简单布局接口
type SimpleLayout interface {
Layout(container Container)
}
```
# File: debug_test.go
```go
package base
import (
"image/color"
"testing"
"github.com/hajimehoshi/ebiten/v2"
"github.com/stretchr/testify/assert"
)
func TestLayoutDebugger(t *testing.T) {
t.Run("NewLayoutDebugger", func(t *testing.T) {
debugger := NewLayoutDebugger()
assert.NotNil(t, debugger, "NewLayoutDebugger should not return nil")
assert.False(t, debugger.IsEnabled(), "Debugger should be disabled by default")
assert.Equal(t, color.RGBA{R: 255, G: 0, B: 0, A: 128}, debugger.color, "Default color should be red with 50% alpha")
})
t.Run("Enable/Disable", func(t *testing.T) {
debugger := NewLayoutDebugger()
debugger.SetEnabled(true)
assert.True(t, debugger.IsEnabled(), "Debugger should be enabled")
debugger.SetEnabled(false)
assert.False(t, debugger.IsEnabled(), "Debugger should be disabled")
})
t.Run("SetColor", func(t *testing.T) {
debugger := NewLayoutDebugger()
testColor := color.RGBA{R: 0, G: 255, B: 0, A: 255}
debugger.SetColor(testColor)
assert.Equal(t, testColor, debugger.color, "Color should be updated")
})
t.Run("DrawDebugInfo", func(t *testing.T) {
debugger := NewLayoutDebugger()
screen := ebiten.NewImage(100, 100)
component := NewBaseComponent()
component.SetBounds(NewBounds(10, 10, 80, 80))
// Test when disabled
debugger.SetEnabled(false)
debugger.DrawDebugInfo(screen, component)
// No assertions needed as disabled debugger shouldn't draw anything
// Test when enabled
debugger.SetEnabled(true)
debugger.DrawDebugInfo(screen, component)
// Visual output testing would require image comparison
})
t.Run("DrawLayoutConstraints", func(t *testing.T) {
debugger := NewLayoutDebugger()
screen := ebiten.NewImage(100, 100)
component := NewBaseComponent()
component.SetBounds(NewBounds(10, 10, 80, 80))
constraints := NewBaseLayoutConstraints()
constraints.SetMinWidth(50)
constraints.SetMinHeight(50)
constraints.SetMaxWidth(100)
constraints.SetMaxHeight(100)
component.SetLayoutConstraints(constraints)
// Test when disabled
debugger.SetEnabled(false)
debugger.DrawLayoutConstraints(screen, component)
// No assertions needed as disabled debugger shouldn't draw anything
// Test when enabled
debugger.SetEnabled(true)
debugger.DrawLayoutConstraints(screen, component)
// Visual output testing would require image comparison
})
t.Run("Container Debug Info", func(t *testing.T) {
debugger := NewLayoutDebugger()
screen := ebiten.NewImage(200, 200)
container := NewBaseContainer()
container.SetBounds(NewBounds(0, 0, 200, 200))
child1 := NewBaseComponent()
child1.SetBounds(NewBounds(10, 10, 80, 80))
container.AddComponent(child1)
child2 := NewBaseComponent()
child2.SetBounds(NewBounds(100, 100, 80, 80))
container.AddComponent(child2)
debugger.SetEnabled(true)
debugger.DrawDebugInfo(screen, container)
// Visual output testing would require image comparison
})
}
func TestComponentBoundsOverlap(t *testing.T) {
t.Run("Overlapping Components", func(t *testing.T) {
comp1 := NewBaseComponent()
comp2 := NewBaseComponent()
comp1.SetBounds(NewBounds(0, 0, 100, 100))
comp2.SetBounds(NewBounds(50, 50, 100, 100))
bounds1 := comp1.GetBounds()
bounds2 := comp2.GetBounds()
assert.True(t, bounds1.Intersects(bounds2), "Components should overlap")
})
t.Run("Non-overlapping Components", func(t *testing.T) {
comp1 := NewBaseComponent()
comp2 := NewBaseComponent()
comp1.SetBounds(NewBounds(0, 0, 100, 100))
comp2.SetBounds(NewBounds(150, 150, 100, 100))
bounds1 := comp1.GetBounds()
bounds2 := comp2.GetBounds()
assert.False(t, bounds1.Intersects(bounds2), "Components should not overlap")
})
t.Run("Adjacent Components", func(t *testing.T) {
comp1 := NewBaseComponent()
comp2 := NewBaseComponent()
comp1.SetBounds(NewBounds(0, 0, 100, 100))
comp2.SetBounds(NewBounds(100, 0, 100, 100))
bounds1 := comp1.GetBounds()
bounds2 := comp2.GetBounds()
assert.False(t, bounds1.Intersects(bounds2), "Adjacent components should not overlap")
})
t.Run("Contained Components", func(t *testing.T) {
comp1 := NewBaseComponent()
comp2 := NewBaseComponent()
comp1.SetBounds(NewBounds(0, 0, 100, 100))
comp2.SetBounds(NewBounds(25, 25, 50, 50))
bounds1 := comp1.GetBounds()
bounds2 := comp2.GetBounds()
assert.True(t, bounds1.Intersects(bounds2), "Contained components should overlap")
})
}
```
# File: types.go
```go
package base
import (
"sync"
)
// Point represents a 2D point with x and y coordinates
type Point struct {
x float64
y float64
}
// NewPoint creates a new Point
func NewPoint(x, y float64) Point {
return Point{x: x, y: y}
}
// X returns the x coordinate
func (p Point) X() float64 {
return p.x
}
// Y returns the y coordinate
func (p Point) Y() float64 {
return p.y
}
// SetX sets the x coordinate
func (p *Point) SetX(x float64) {
p.x = x
}
// SetY sets the y coordinate
func (p *Point) SetY(y float64) {
p.y = y
}
// Equals returns true if the points are equal
func (p Point) Equals(other Point) bool {
return p.x == other.x && p.y == other.y
}
// Direction represents a layout direction
type Direction int
const (
// DirectionHorizontal represents horizontal direction
DirectionHorizontal Direction = iota
// DirectionVertical represents vertical direction
DirectionVertical
)
// Size represents a 2D size with width and height
type Size struct {
width float64
height float64
}
// NewSize creates a new Size
func NewSize(width, height float64) Size {
return Size{width: width, height: height}
}
// Width returns the width
func (s Size) Width() float64 {
return s.width
}
// Height returns the height
func (s Size) Height() float64 {
return s.height
}
// SetWidth sets the width
func (s *Size) SetWidth(width float64) {
s.width = width
}
// SetHeight sets the height
func (s *Size) SetHeight(height float64) {
s.height = height
}
// Equals returns true if the sizes are equal
func (s Size) Equals(other Size) bool {
return s.width == other.width && s.height == other.height
}
// Color represents an RGBA color
type Color struct {
r, g, b, a float64
}
// NewColor creates a new Color instance
func NewColor(r, g, b, a float64) Color {
return Color{r: r, g: g, b: b, a: a}
}
// R returns the red component
func (c Color) R() float64 {
return c.r
}
// G returns the green component
func (c Color) G() float64 {
return c.g
}
// B returns the blue component
func (c Color) B() float64 {
return c.b
}
// A returns the alpha component
func (c Color) A() float64 {
return c.a
}
// SetR sets the red component
func (c *Color) SetR(r float64) {
c.r = r
}
// SetG sets the green component
func (c *Color) SetG(g float64) {
c.g = g
}
// SetB sets the blue component
func (c *Color) SetB(b float64) {
c.b = b
}
// SetA sets the alpha component
func (c *Color) SetA(a float64) {
c.a = a
}
// Equals returns true if the colors are equal
func (c Color) Equals(other Color) bool {
return c.r == other.r && c.g == other.g && c.b == other.b && c.a == other.a
}
// 添加 ComponentSet 类型,用于存储已处理的组件
type ComponentSet struct {
sync.RWMutex
components map[Component]struct{}
}
func NewComponentSet() *ComponentSet {
return &ComponentSet{
components: make(map[Component]struct{}),
}
}
func (cs *ComponentSet) Add(c Component) {
cs.Lock()
defer cs.Unlock()
cs.components[c] = struct{}{}
}
func (cs *ComponentSet) Contains(c Component) bool {
cs.RLock()
defer cs.RUnlock()
_, exists := cs.components[c]
return exists
}
func (cs *ComponentSet) Clear() {
cs.Lock()
defer cs.Unlock()
cs.components = make(map[Component]struct{})
}
func (cs *ComponentSet) Remove(c Component) {
cs.Lock()
defer cs.Unlock()
delete(cs.components, c)
}
func (cs *ComponentSet) Size() int {
cs.RLock()
defer cs.RUnlock()
return len(cs.components)
}
func (cs *ComponentSet) ForEach(fn func(Component)) {
cs.RLock()
defer cs.RUnlock()
for c := range cs.components {
fn(c)
}
}
// Rect 定义矩形接口
type Rect interface {
X() float64
Y() float64
Width() float64
Height() float64
SetX(x float64)
SetY(y float64)
SetWidth(width float64)
SetHeight(height float64)
Contains(x, y float64) bool
Intersects(other Rect) bool
Clone() Rect
}
// BaseRect 提供矩形的基本实现
type BaseRect struct {
x, y, width, height float64
}
func (r *BaseRect) X() float64 { return r.x }
func (r *BaseRect) Y() float64 { return r.y }
func (r *BaseRect) Width() float64 { return r.width }
func (r *BaseRect) Height() float64 { return r.height }
func (r *BaseRect) SetX(x float64) { r.x = x }
func (r *BaseRect) SetY(y float64) { r.y = y }
func (r *BaseRect) SetWidth(width float64) { r.width = width }
func (r *BaseRect) SetHeight(height float64) { r.height = height }
func (r *BaseRect) Contains(x, y float64) bool {
return x >= r.x && x <= r.x+r.width &&
y >= r.y && y <= r.y+r.height
}
func (r *BaseRect) Intersects(other Rect) bool {
return !(other.X() > r.x+r.width ||
other.X()+other.Width() < r.x ||
other.Y() > r.y+r.height ||
other.Y()+other.Height() < r.y)
}
func (r *BaseRect) Clone() Rect {
return &BaseRect{
x: r.x,
y: r.y,
width: r.width,
height: r.height,
}
}
// Layout 接口定义
type Layout interface {
Layout(container Container)
}
```
# File: event.go
```go
package base
import (
"sync"
"plants-vs-zombies/internal/core/event"
)
// EventEmitter 定义事件发射器接口
type EventEmitter interface {
// 添加事件监听器
AddListener(eventType event.EventType, handler event.Handler)
// 移除事件监听器
RemoveListener(eventType event.EventType, handler event.Handler)
// 发送事件
EmitEvent(evt event.Event)
}
// BaseEventEmitter 提供事件发射器的基本实现
type BaseEventEmitter struct {
eventTarget *event.BaseEventTarget
mu sync.RWMutex
}
// NewBaseEventEmitter 创建新的事件发射器
func NewBaseEventEmitter() *BaseEventEmitter {
return &BaseEventEmitter{
eventTarget: event.NewBaseEventTarget(),
}
}
// AddListener 添加事件监听器
func (e *BaseEventEmitter) AddListener(eventType event.EventType, handler event.Handler) {
e.mu.Lock()
defer e.mu.Unlock()
e.eventTarget.AddEventListener(eventType, event.EventPhaseTarget, handler, event.PriorityNormal)
}
// RemoveListener 移除事件监听器
func (e *BaseEventEmitter) RemoveListener(eventType event.EventType, handler event.Handler) {
e.mu.Lock()
defer e.mu.Unlock()
e.eventTarget.RemoveEventListener(eventType, event.EventPhaseTarget, handler)
}
// EmitEvent 发送事件
func (e *BaseEventEmitter) EmitEvent(evt event.Event) {
e.mu.RLock()
defer e.mu.RUnlock()
e.eventTarget.DispatchEvent(evt)
}
// AddEventHandler 添加事件处理器(带优先级)
func (e *BaseEventEmitter) AddEventHandler(eventType event.EventType, handler event.Handler, priority int) {
e.mu.Lock()
defer e.mu.Unlock()
e.eventTarget.AddEventListener(eventType, event.EventPhaseTarget, handler, priority)
}
// RemoveEventHandler 移除事件处理器
func (e *BaseEventEmitter) RemoveEventHandler(eventType event.EventType, handler event.Handler) {
e.mu.Lock()
defer e.mu.Unlock()
e.eventTarget.RemoveEventListener(eventType, event.EventPhaseTarget, handler)
}
// DispatchEvent 分发事件
func (e *BaseEventEmitter) DispatchEvent(evt event.Event) bool {
e.mu.RLock()
defer e.mu.RUnlock()
if evt == nil || e.eventTarget == nil {
return false
}
// 设置事件目标
if evt.Target() == nil {
evt.SetTarget(e.eventTarget)
}
evt.SetCurrentTarget(e.eventTarget)
// 先执行捕获阶段
if evt.Bubbles() {
e.eventTarget.AddEventListener(evt.Type(), event.EventPhaseCapturing,
event.HandlerFunc(func(e event.Event) bool {
return true
}), event.PriorityNormal)
e.eventTarget.DispatchEvent(evt)
}
// 执行目标阶段
handled := e.eventTarget.DispatchEvent(evt)
// 最后执行冒泡阶段
if evt.Bubbles() && !handled {
e.eventTarget.AddEventListener(evt.Type(), event.EventPhaseBubbling,
event.HandlerFunc(func(e event.Event) bool {
return true
}), event.PriorityNormal)
handled = e.eventTarget.DispatchEvent(evt)
}
return handled
}
// EventHandler 实现 event.Handler 接口的事件处理器
type EventHandler struct {
id int64
phase event.EventPhase
handler func(event.Event) bool
}
// NewEventHandler 创建新的事件处理器
func NewEventHandler(handler func(event.Event) bool) *EventHandler {
return &EventHandler{
id: generateHandlerID(),
phase: event.EventPhaseTarget,
handler: handler,
}
}
// GetID 实现 Handler 接口
func (h *EventHandler) GetID() int64 {
return h.id
}
// GetPhase 实现 Handler 接口
func (h *EventHandler) GetPhase() event.EventPhase {
return h.phase
}
// SetPhase 设置事件处理阶段
func (h *EventHandler) SetPhase(phase event.EventPhase) {
h.phase = phase
}
// HandleEvent 实现 Handler 接口
func (h *EventHandler) HandleEvent(evt event.Event) bool {
return h.handler(evt)
}
var handlerIDCounter int64
var handlerIDMutex sync.Mutex
func generateHandlerID() int64 {
handlerIDMutex.Lock()
defer handlerIDMutex.Unlock()
handlerIDCounter++
return handlerIDCounter
}
```
# File: types_test.go
```go
package base
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestBounds(t *testing.T) {
t.Run("Basic bounds operations", func(t *testing.T) {
bounds := NewBounds(10, 20, 100, 200)
// Test getters
assert.Equal(t, float64(10), bounds.X())
assert.Equal(t, float64(20), bounds.Y())
assert.Equal(t, float64(100), bounds.Width())
assert.Equal(t, float64(200), bounds.Height())
// Test setters
bounds.SetX(30)
bounds.SetY(40)
bounds.SetWidth(150)
bounds.SetHeight(250)
assert.Equal(t, float64(30), bounds.X())
assert.Equal(t, float64(40), bounds.Y())
assert.Equal(t, float64(150), bounds.Width())
assert.Equal(t, float64(250), bounds.Height())
})
t.Run("Contains point", func(t *testing.T) {
bounds := NewBounds(10, 20, 100, 200)
// Test points inside bounds
assert.True(t, bounds.Contains(15, 25))
assert.True(t, bounds.Contains(50, 100))
assert.True(t, bounds.Contains(109, 219))
// Test points outside bounds
assert.False(t, bounds.Contains(5, 25))
assert.False(t, bounds.Contains(15, 15))
assert.False(t, bounds.Contains(111, 100))
assert.False(t, bounds.Contains(50, 221))
})
t.Run("Bounds intersection", func(t *testing.T) {
bounds1 := NewBounds(10, 20, 100, 200)
bounds2 := NewBounds(50, 60, 100, 200)
bounds3 := NewBounds(200, 300, 100, 200)
// Test intersecting bounds
assert.True(t, bounds1.Intersects(bounds2))
assert.True(t, bounds2.Intersects(bounds1))
// Test non-intersecting bounds
assert.False(t, bounds1.Intersects(bounds3))
assert.False(t, bounds3.Intersects(bounds1))
})
}
func TestPoint(t *testing.T) {
t.Run("Basic point operations", func(t *testing.T) {
point := NewPoint(10, 20)
// Test getters
assert.Equal(t, float64(10), point.X())
assert.Equal(t, float64(20), point.Y())
// Test setters
point.SetX(30)
point.SetY(40)
assert.Equal(t, float64(30), point.X())
assert.Equal(t, float64(40), point.Y())
})
t.Run("Point comparison", func(t *testing.T) {
point1 := NewPoint(10, 20)
point2 := NewPoint(10, 20)
point3 := NewPoint(30, 40)
assert.True(t, point1.Equals(point2))
assert.True(t, point2.Equals(point1))
assert.False(t, point1.Equals(point3))
assert.False(t, point3.Equals(point1))
})
}
func TestSize(t *testing.T) {
t.Run("Basic size operations", func(t *testing.T) {
size := NewSize(100, 200)
// Test getters
assert.Equal(t, float64(100), size.Width())
assert.Equal(t, float64(200), size.Height())
// Test setters
size.SetWidth(150)
size.SetHeight(250)
assert.Equal(t, float64(150), size.Width())
assert.Equal(t, float64(250), size.Height())
})
t.Run("Size comparison", func(t *testing.T) {
size1 := NewSize(100, 200)
size2 := NewSize(100, 200)
size3 := NewSize(150, 250)
assert.True(t, size1.Equals(size2))
assert.True(t, size2.Equals(size1))
assert.False(t, size1.Equals(size3))
assert.False(t, size3.Equals(size1))
})
}
func TestMargins(t *testing.T) {
t.Run("Basic margins operations", func(t *testing.T) {
margins := NewMargins(10, 20, 30, 40)
// Test values
assert.Equal(t, float64(10), margins.Top)
assert.Equal(t, float64(20), margins.Right)
assert.Equal(t, float64(30), margins.Bottom)
assert.Equal(t, float64(40), margins.Left)
// Test spacing calculations
assert.Equal(t, float64(60), margins.GetHorizontalSpacing()) // Left + Right
assert.Equal(t, float64(40), margins.GetVerticalSpacing()) // Top + Bottom
})
t.Run("String representation", func(t *testing.T) {
margins := NewMargins(10, 20, 30, 40)
expected := "Margins(T:10.0 R:20.0 B:30.0 L:40.0)"
assert.Equal(t, expected, margins.String())
})
}
func TestColor(t *testing.T) {
t.Run("Basic color operations", func(t *testing.T) {
color := NewColor(1.0, 0.5, 0.25, 0.75)
// Test getters
assert.Equal(t, float64(1.0), color.R())
assert.Equal(t, float64(0.5), color.G())
assert.Equal(t, float64(0.25), color.B())
assert.Equal(t, float64(0.75), color.A())
// Test setters
color.SetR(0.8)
color.SetG(0.6)
color.SetB(0.4)
color.SetA(0.9)
assert.Equal(t, float64(0.8), color.R())
assert.Equal(t, float64(0.6), color.G())
assert.Equal(t, float64(0.4), color.B())
assert.Equal(t, float64(0.9), color.A())
})
t.Run("Color comparison", func(t *testing.T) {
color1 := NewColor(1.0, 0.5, 0.25, 0.75)
color2 := NewColor(1.0, 0.5, 0.25, 0.75)
color3 := NewColor(0.8, 0.6, 0.4, 0.9)
assert.True(t, color1.Equals(color2))
assert.True(t, color2.Equals(color1))
assert.False(t, color1.Equals(color3))
assert.False(t, color3.Equals(color1))
})
}
```
# File: debug.go
```go
package base
import (
"fmt"
"image/color"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
)
// LayoutDebugger 提供UI布局调试功能
type LayoutDebugger struct {
enabled bool
color color.Color
}
// NewLayoutDebugger 创建新的布局调试器
func NewLayoutDebugger() *LayoutDebugger {
return &LayoutDebugger{
enabled: false,
color: color.RGBA{R: 255, G: 0, B: 0, A: 128},
}
}
// SetEnabled 启用或禁用调试器
func (d *LayoutDebugger) SetEnabled(enabled bool) {
d.enabled = enabled
}
// IsEnabled 返回调试器是否启用
func (d *LayoutDebugger) IsEnabled() bool {
return d.enabled
}
// SetColor 设置调试颜色
func (d *LayoutDebugger) SetColor(c color.Color) {
d.color = c
}
// DrawDebugInfo 绘制组件的调试信息
func (d *LayoutDebugger) DrawDebugInfo(screen *ebiten.Image, component Component) {
if !d.enabled {
return
}
bounds := component.GetBounds()
if bounds == nil {
return
}
// 绘制组件边界
ebitenutil.DrawRect(screen, bounds.X(), bounds.Y(), bounds.Width(), bounds.Height(), d.color)
// 绘制组件信息
info := fmt.Sprintf("Pos: (%.1f, %.1f)\nSize: %.1fx%.1f",
bounds.X(), bounds.Y(), bounds.Width(), bounds.Height())
ebitenutil.DebugPrintAt(screen, info, int(bounds.X()), int(bounds.Y()))
// 如果是容器,绘制子组件的调试信息
if container, ok := component.(Container); ok {
for _, child := range container.GetComponents() {
d.DrawDebugInfo(screen, child)
}
}
}
// DrawLayoutConstraints 绘制布局约束的调试信息
func (d *LayoutDebugger) DrawLayoutConstraints(screen *ebiten.Image, component Component) {
if !d.enabled {
return
}
constraints := component.GetLayoutConstraints()
if constraints == nil {
return
}
bounds := component.GetBounds()
if bounds == nil {
return
}
// 绘制最小/最大尺寸信息
info := fmt.Sprintf("Min: %.1fx%.1f\nMax: %.1fx%.1f",
constraints.MinWidth(), constraints.MinHeight(),
constraints.MaxWidth(), constraints.MaxHeight())
ebitenutil.DebugPrintAt(screen, info,
int(bounds.X()), int(bounds.Y()+bounds.Height()))
// 绘制边距信息
margin := constraints.Margin()
padding := constraints.Padding()
marginInfo := fmt.Sprintf("Margin: T:%.1f R:%.1f B:%.1f L:%.1f",
margin.Top, margin.Right, margin.Bottom, margin.Left)
paddingInfo := fmt.Sprintf("Padding: T:%.1f R:%.1f B:%.1f L:%.1f",
padding.Top, padding.Right, padding.Bottom, padding.Left)
ebitenutil.DebugPrintAt(screen, marginInfo,
int(bounds.X()), int(bounds.Y()+bounds.Height()+20))
ebitenutil.DebugPrintAt(screen, paddingInfo,
int(bounds.X()), int(bounds.Y()+bounds.Height()+40))
}
```
# File: margins.go
```go
package base
import (
"fmt"
)
type Margins struct {
Left float64
Top float64
Right float64
Bottom float64
}
func NewMargins(top, right, bottom, left float64) *Margins {
return &Margins{
Top: top,
Right: right,
Bottom: bottom,
Left: left,
}
}
// GetHorizontalSpacing 返回水平方向的总间距
func (m Margins) GetHorizontalSpacing() float64 {
return m.Left + m.Right
}
// GetVerticalSpacing 返回垂直方向的总间距
func (m Margins) GetVerticalSpacing() float64 {
return m.Top + m.Bottom
}
// String 返回边距的字符串表示
func (m Margins) String() string {
return fmt.Sprintf("Margins(T:%.1f R:%.1f B:%.1f L:%.1f)",
m.Top, m.Right, m.Bottom, m.Left)
}
// Clone 克隆边距
func (m *Margins) Clone() *Margins {
if m == nil {
return nil
}
return &Margins{
Top: m.Top,
Right: m.Right,
Bottom: m.Bottom,
Left: m.Left,
}
}
```