Camera represents a simple 2D camera state.
Manager handles input bindings and state.
BindKey associates a keyboard key with an action.
{ m.keyBindings[key] = action }
RebindKey changes the key mapped to an action.
{
for k, a := range m.keyBindings {
if a == action {
delete(m.keyBindings, k)
break
}
}
m.keyBindings[key] = action
}
BindMouse associates a mouse button with an action.
{ m.mouseBindings[button] = action }
RebindMouse changes the button mapped to an action.
{
for b, a := range m.mouseBindings {
if a == action {
delete(m.mouseBindings, b)
break
}
}
m.mouseBindings[button] = action
}
IsActive reports whether an action is currently engaged.
{ return m.active[action] }
DragRect returns the start and end of the current drag along with its state.
{
return m.dragStart, m.dragEnd, m.dragging
}
Camera returns a copy of the current camera state.
{ return m.camera }
Pan translates the camera by dx, dy.
{
m.camera.Position = m.camera.Position.Add(math.Vec2{dx, dy})
}
Zoom adjusts the camera zoom level.
{ m.camera.Zoom += delta }
Rotate adjusts the camera rotation.
{ m.camera.Rotation += delta }
{
if action, ok := m.keyBindings[key]; ok {
m.active[action] = true
}
}
{
if action, ok := m.keyBindings[key]; ok {
delete(m.active, action)
}
}
{
if action, ok := m.mouseBindings[button]; ok {
m.active[action] = true
}
if button == 0 {
m.dragging = true
m.dragStart = math.Vec2{x, y}
m.dragEnd = m.dragStart
}
m.lastPos = math.Vec2{x, y}
}
{
if action, ok := m.mouseBindings[button]; ok {
delete(m.active, action)
}
if button == 0 {
m.dragging = false
}
m.lastPos = math.Vec2{x, y}
}
{
pos := math.Vec2{x, y}
if m.dragging {
m.dragEnd = pos
}
dx := pos.X - m.lastPos.X
dy := pos.Y - m.lastPos.Y
if m.IsActive("pan") {
m.Pan(dx, dy)
}
if m.IsActive("rotate") {
m.Rotate(dx)
}
m.lastPos = pos
}
{ m.Zoom(-delta / 100) }
{
return &Manager{
keyBindings: make(map[string]string),
mouseBindings: make(map[int]string),
active: make(map[string]bool),
camera: Camera{Zoom: 1},
}
}
New creates a Manager and wires browser event listeners.
{
m := newManager()
events.OnKeyDown(func(evt js.Value) {
m.handleKeyDown(evt.Get("key").String())
})
events.OnKeyUp(func(evt js.Value) {
m.handleKeyUp(evt.Get("key").String())
})
events.On("mousedown", js.Document(), func(evt js.Value) {
m.handleMouseDown(evt.Get("button").Int(), float32(evt.Get("clientX").Float()), float32(evt.Get("clientY").Float()))
})
events.On("mouseup", js.Document(), func(evt js.Value) {
m.handleMouseUp(evt.Get("button").Int(), float32(evt.Get("clientX").Float()), float32(evt.Get("clientY").Float()))
})
events.On("mousemove", js.Document(), func(evt js.Value) {
m.handleMouseMove(float32(evt.Get("clientX").Float()), float32(evt.Get("clientY").Float()))
})
events.On("wheel", js.Document(), func(evt js.Value) {
m.handleWheel(float32(evt.Get("deltaY").Float()))
})
return m
}
New creates a Manager without wiring event listeners.
{ return newManager() }
{
m := New()
m.BindKey("jump", "Space")
m.handleKeyDown("Space")
if !m.IsActive("jump") {
t.Fatalf("action not active on key down")
}
m.handleKeyUp("Space")
if m.IsActive("jump") {
t.Fatalf("action still active after key up")
}
}
{
m := New()
m.handleMouseDown(0, 10, 20)
m.handleMouseMove(30, 40)
start, end, dragging := m.DragRect()
if !dragging {
t.Fatalf("expected dragging state")
}
if start != (math.Vec2{10, 20}) || end != (math.Vec2{30, 40}) {
t.Fatalf("unexpected drag rect %v %v", start, end)
}
m.handleMouseUp(0, 30, 40)
_, _, dragging = m.DragRect()
if dragging {
t.Fatalf("drag state not cleared")
}
}
{
m := New()
m.BindMouse("pan", 1)
m.handleMouseDown(1, 0, 0)
m.handleMouseMove(5, -5)
cam := m.Camera()
if cam.Position != (math.Vec2{5, -5}) {
t.Fatalf("pan not applied, got %v", cam.Position)
}
m.handleWheel(-120)
cam = m.Camera()
if cam.Zoom <= 1 {
t.Fatalf("zoom not applied, got %v", cam.Zoom)
}
m.BindMouse("rotate", 2)
m.handleMouseDown(2, 0, 0)
m.handleMouseMove(10, 0)
cam = m.Camera()
if cam.Rotation == 0 {
t.Fatalf("rotation not applied")
}
}
import "github.com/rfwlab/rfw/v1/math"
import "github.com/rfwlab/rfw/v1/events"
import "github.com/rfwlab/rfw/v1/js"
js
import "testing"
import "github.com/rfwlab/rfw/v1/math"