280 lines
7.0 KiB
Go
280 lines
7.0 KiB
Go
package editor
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
|
|
"aego/core/mathx"
|
|
"aego/editor/core/services"
|
|
"aego/engine"
|
|
"aego/modules/input"
|
|
raw "aego/platform/input"
|
|
"aego/ui/core"
|
|
"aego/ui/widgets"
|
|
)
|
|
|
|
const (
|
|
DevicesID = input.ID + ".devices"
|
|
FocusID = input.ID + ".focus"
|
|
virtualPad = uint32(0xAE900001)
|
|
stickR = float32(44)
|
|
)
|
|
|
|
type Devices struct {
|
|
world func() *engine.World
|
|
input func() *raw.State
|
|
virt bool
|
|
held [raw.PadButtonCount]bool
|
|
sticks [2]mathx.Vec2
|
|
player int
|
|
}
|
|
|
|
func NewDevices(world func() *engine.World, in func() *raw.State) *Devices {
|
|
return &Devices{world: world, input: in}
|
|
}
|
|
|
|
func (*Devices) ID() string {
|
|
return DevicesID
|
|
}
|
|
|
|
func (*Devices) Title() string {
|
|
return "Input Devices"
|
|
}
|
|
|
|
func vec(v mathx.Vec2) string {
|
|
return "(" + strconv.FormatFloat(float64(v.X), 'f', 2, 32) + ", " + strconv.FormatFloat(float64(v.Y), 'f', 2, 32) + ")"
|
|
}
|
|
|
|
func point(v mathx.Vec2) string {
|
|
return "(" + strconv.Itoa(int(v.X)) + ", " + strconv.Itoa(int(v.Y)) + ")"
|
|
}
|
|
|
|
func padState(in *raw.State, i int) string {
|
|
var b strings.Builder
|
|
for k := raw.PadButton(0); k < raw.PadButtonCount; k++ {
|
|
if in.PadDown(i, k) {
|
|
b.WriteString(k.String() + " ")
|
|
}
|
|
}
|
|
for a := raw.PadAxis(0); a < raw.PadAxisCount; a++ {
|
|
if v := in.PadAxisValue(i, a); mathx.Abs(v) > 0.05 {
|
|
b.WriteString(a.String() + "=" + strconv.FormatFloat(float64(v), 'f', 2, 32) + " ")
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func deviceName(d int) string {
|
|
switch {
|
|
case d < 0:
|
|
return "none"
|
|
case d == 0:
|
|
return "Keyboard & Mouse"
|
|
}
|
|
return "Pad " + strconv.Itoa(d)
|
|
}
|
|
|
|
func (d *Devices) Draw(f *services.Frame, r mathx.Rect) {
|
|
ui := f.UI
|
|
var in *raw.State
|
|
if d.input != nil {
|
|
in = d.input()
|
|
}
|
|
widgets.Label(ui, "Devices")
|
|
if in != nil {
|
|
widgets.LabelDim(ui, "Keyboard & Mouse cursor "+point(in.MousePos())+" keys down "+strconv.Itoa(in.DownCount()))
|
|
for i := 0; i < raw.MaxPads; i++ {
|
|
if in.PadConnected(i) {
|
|
widgets.LabelDim(ui, "Pad "+strconv.Itoa(i+1)+": "+in.PadName(i)+" "+padState(in, i))
|
|
}
|
|
}
|
|
d.virtual(ui, in)
|
|
}
|
|
widgets.Separator(ui)
|
|
d.actions(ui)
|
|
}
|
|
|
|
type padKey struct {
|
|
b raw.PadButton
|
|
label string
|
|
r mathx.Rect
|
|
}
|
|
|
|
func padLayout(area mathx.Rect) ([]padKey, [2]mathx.Vec2) {
|
|
const cs, gap, row = float32(26), float32(2), float32(22)
|
|
step := cs + gap
|
|
mid := area.Y + row + 6 + stickR + 4
|
|
lc := mathx.Vec2{X: area.X + stickR + 4, Y: mid}
|
|
dc := mathx.Vec2{X: lc.X + stickR + 16 + 1.5*step, Y: mid}
|
|
fc := mathx.Vec2{X: dc.X + 3*step + 24, Y: mid}
|
|
rc := mathx.Vec2{X: fc.X + 1.5*step + 16 + stickR, Y: mid}
|
|
end := rc.X + stickR + 4
|
|
cell := func(c mathx.Vec2, dx, dy float32) mathx.Rect {
|
|
return mathx.Rect{X: c.X + dx*step - cs/2, Y: c.Y + dy*step - cs/2, W: cs, H: cs}
|
|
}
|
|
top := func(x, w float32) mathx.Rect {
|
|
return mathx.Rect{X: x, Y: area.Y, W: w, H: row}
|
|
}
|
|
cx := (dc.X + fc.X) / 2
|
|
keys := []padKey{
|
|
{raw.PadLB, "LB", top(area.X, 44)},
|
|
{raw.PadL3, "L3", top(area.X+48, 44)},
|
|
{raw.PadBack, "Back", top(cx-78, 52)},
|
|
{raw.PadGuide, "Guide", top(cx-24, 48)},
|
|
{raw.PadStart, "Start", top(cx+28, 52)},
|
|
{raw.PadR3, "R3", top(end-92, 44)},
|
|
{raw.PadRB, "RB", top(end-44, 44)},
|
|
{raw.PadUp, "↑", cell(dc, 0, -1)},
|
|
{raw.PadDown, "↓", cell(dc, 0, 1)},
|
|
{raw.PadLeft, "←", cell(dc, -1, 0)},
|
|
{raw.PadRight, "→", cell(dc, 1, 0)},
|
|
{raw.PadY, "Y", cell(fc, 0, -1)},
|
|
{raw.PadA, "A", cell(fc, 0, 1)},
|
|
{raw.PadX, "X", cell(fc, -1, 0)},
|
|
{raw.PadB, "B", cell(fc, 1, 0)},
|
|
}
|
|
return keys, [2]mathx.Vec2{lc, rc}
|
|
}
|
|
|
|
func (d *Devices) SetVirtual(in *raw.State, on bool) {
|
|
if in == nil || on == d.virt {
|
|
return
|
|
}
|
|
d.virt = on
|
|
if on {
|
|
in.ApplyPadAdded(virtualPad, "Virtual Gamepad")
|
|
return
|
|
}
|
|
in.ApplyPadRemoved(virtualPad)
|
|
d.held = [raw.PadButtonCount]bool{}
|
|
d.sticks = [2]mathx.Vec2{}
|
|
}
|
|
|
|
func (d *Devices) virtual(ui *core.Context, in *raw.State) {
|
|
th := &ui.Theme
|
|
on := d.virt
|
|
label := "Virtual Gamepad"
|
|
row := ui.Cursor().Next(0)
|
|
br := mathx.Rect{X: row.X, Y: row.Y, W: ui.TextSize(label).X + 24, H: row.H}
|
|
if widgets.ToggleButton(ui, ui.ID("vpad"), br, label, &on) {
|
|
d.SetVirtual(in, on)
|
|
}
|
|
if !d.virt {
|
|
return
|
|
}
|
|
area := ui.Cursor().Next(22 + 6 + 2*stickR + 8)
|
|
keys, centers := padLayout(area)
|
|
for _, k := range keys {
|
|
id := core.IDSeq(ui.ID("vb"), int(k.b))
|
|
if ui.Pressed(id, k.r) {
|
|
ui.CaptureMouse(id)
|
|
}
|
|
held := ui.Active() == id && ui.MouseDown(raw.MouseLeft)
|
|
if ui.Active() == id && !ui.MouseDown(raw.MouseLeft) {
|
|
ui.ReleaseMouse()
|
|
}
|
|
col := ui.WidgetColor(id)
|
|
if held {
|
|
col = th.Accent
|
|
}
|
|
ui.Box(k.r, th.Radius.Button, col, th.Border)
|
|
ui.TextIn(k.r, k.label, th.Text, core.AlignCenter)
|
|
if held != d.held[k.b] {
|
|
d.held[k.b] = held
|
|
in.ApplyPadButton(virtualPad, k.b, held)
|
|
}
|
|
}
|
|
for s := 0; s < 2; s++ {
|
|
c := centers[s]
|
|
box := mathx.Rect{X: c.X - stickR, Y: c.Y - stickR, W: 2 * stickR, H: 2 * stickR}
|
|
id := core.IDSeq(ui.ID("vs"), s)
|
|
if ui.Pressed(id, box) {
|
|
ui.CaptureMouse(id)
|
|
}
|
|
var v mathx.Vec2
|
|
if ui.Active() == id {
|
|
if ui.MouseDown(raw.MouseLeft) {
|
|
v = ui.Mouse().Sub(c).Scale(1 / stickR)
|
|
if l := v.Len(); l > 1 {
|
|
v = v.Scale(1 / l)
|
|
}
|
|
} else {
|
|
ui.ReleaseMouse()
|
|
}
|
|
}
|
|
ui.Box(box, stickR, th.Widget, th.Border)
|
|
k := c.Add(v.Scale(stickR - 12))
|
|
ui.RoundRect(mathx.Rect{X: k.X - 12, Y: k.Y - 12, W: 24, H: 24}, 12, th.Accent)
|
|
if v != d.sticks[s] {
|
|
d.sticks[s] = v
|
|
ax := raw.PadAxis(s * 2)
|
|
in.ApplyPadAxis(virtualPad, ax, v.X)
|
|
in.ApplyPadAxis(virtualPad, ax+1, v.Y)
|
|
}
|
|
}
|
|
}
|
|
|
|
func valueText(i input.Info) string {
|
|
switch i.Kind {
|
|
case input.Button:
|
|
if i.Down {
|
|
return "held"
|
|
}
|
|
return ""
|
|
case input.Axis1D:
|
|
return strconv.FormatFloat(float64(i.Value.X), 'f', 2, 32)
|
|
}
|
|
return vec(i.Value)
|
|
}
|
|
|
|
func (d *Devices) actions(ui *core.Context) {
|
|
th := &ui.Theme
|
|
var s *input.Service
|
|
if d.world != nil {
|
|
s = input.Of(d.world())
|
|
}
|
|
if s == nil {
|
|
widgets.LabelDim(ui, "Live action values appear here while Play runs")
|
|
return
|
|
}
|
|
if n := s.Players(); n > 1 {
|
|
names := make([]string, n)
|
|
for i := range names {
|
|
names[i] = "Player " + strconv.Itoa(i+1)
|
|
}
|
|
widgets.Combo(ui, ui.ID("player"), widgets.Field(ui, "Player"), &d.player, names, false)
|
|
}
|
|
d.player = min(d.player, s.Players()-1)
|
|
widgets.LabelDim(ui, "Active device: "+deviceName(s.ActiveDevice(d.player)))
|
|
s.Each(d.player, func(i input.Info) {
|
|
r := ui.Cursor().Next(0)
|
|
col := th.Text
|
|
if !i.Active {
|
|
col = th.TextDisabled
|
|
}
|
|
ui.PushClip(r)
|
|
ui.Text(i.Name, mathx.Vec2{X: r.X + 4, Y: ui.TextY(r)}, col)
|
|
bar := mathx.Rect{X: r.X + r.W*0.3, Y: r.Y + 4, W: r.W * 0.35, H: r.H - 8}
|
|
ui.RoundRect(bar, 3, th.Widget)
|
|
switch i.Kind {
|
|
case input.Axis1D:
|
|
v := mathx.Clamp(i.Value.X, -1, 1)
|
|
mid := bar.X + bar.W/2
|
|
w := bar.W / 2 * mathx.Abs(v)
|
|
x := mid
|
|
if v < 0 {
|
|
x = mid - w
|
|
}
|
|
ui.Rect(mathx.Rect{X: x, Y: bar.Y, W: w, H: bar.H}, th.Accent)
|
|
case input.Axis2D:
|
|
ui.Rect(mathx.Rect{X: bar.X, Y: bar.Y, W: bar.W * mathx.Clamp(i.Value.Len(), 0, 1), H: bar.H}, th.Accent)
|
|
default:
|
|
if i.Down {
|
|
ui.RoundRect(bar, 3, th.Accent)
|
|
}
|
|
}
|
|
ui.Text(valueText(i)+" "+i.Glyph, mathx.Vec2{X: bar.X + bar.W + 8, Y: ui.TextY(r)}, th.TextDim)
|
|
ui.PopClip()
|
|
})
|
|
}
|