Noah Petherbridge
4de0126b19
Adds support for Xbox and Nintendo style game controllers. The gamepad controls are documented on the README and in the game's Settings window. The buttons are not customizable yet, except that the player can choose between two button styles: * X Style (default): "A" button is on the bottom and "B" on the right. * N Style: swaps the A/B and the X/Y buttons to use a Nintendo-style layout instead of an Xbox-style.
807 lines
17 KiB
Go
807 lines
17 KiB
Go
package windows
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
|
"git.kirsle.net/apps/doodle/pkg/gamepad"
|
|
"git.kirsle.net/apps/doodle/pkg/log"
|
|
"git.kirsle.net/apps/doodle/pkg/native"
|
|
"git.kirsle.net/apps/doodle/pkg/shmem"
|
|
magicform "git.kirsle.net/apps/doodle/pkg/uix/magic-form"
|
|
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
|
"git.kirsle.net/apps/doodle/pkg/userdir"
|
|
"git.kirsle.net/go/render"
|
|
"git.kirsle.net/go/ui"
|
|
"git.kirsle.net/go/ui/style"
|
|
)
|
|
|
|
// Settings window.
|
|
type Settings struct {
|
|
// Settings passed in by doodle
|
|
Supervisor *ui.Supervisor
|
|
Engine render.Engine
|
|
|
|
// Boolean bindings.
|
|
DebugOverlay *bool
|
|
DebugCollision *bool
|
|
HorizontalToolbars *bool
|
|
EnableFeatures *bool
|
|
CrosshairSize *int
|
|
CrosshairColor *render.Color
|
|
HideTouchHints *bool
|
|
DisableAutosave *bool
|
|
ControllerStyle *int
|
|
|
|
// Configuration options.
|
|
SceneName string // name of scene which called this window
|
|
ActiveTab string // specify the tab to open
|
|
OnApply func()
|
|
}
|
|
|
|
// MakeSettingsWindow initializes a settings window for any scene.
|
|
// The window width/height are the actual SDL2 window dimensions.
|
|
func MakeSettingsWindow(windowWidth, windowHeight int, cfg Settings) *ui.Window {
|
|
win := NewSettingsWindow(cfg)
|
|
win.Compute(cfg.Engine)
|
|
win.Supervise(cfg.Supervisor)
|
|
|
|
// Center the window.
|
|
size := win.Size()
|
|
win.MoveTo(render.Point{
|
|
X: (windowWidth / 2) - (size.W / 2),
|
|
Y: (windowHeight / 2) - (size.H / 2),
|
|
})
|
|
|
|
return win
|
|
}
|
|
|
|
// NewSettingsWindow initializes the window.
|
|
func NewSettingsWindow(cfg Settings) *ui.Window {
|
|
var (
|
|
Width = 400
|
|
Height = 400
|
|
)
|
|
|
|
window := ui.NewWindow("Settings")
|
|
window.SetButtons(ui.CloseButton)
|
|
window.Configure(ui.Config{
|
|
Width: Width,
|
|
Height: Height,
|
|
Background: render.Grey,
|
|
})
|
|
|
|
///////////
|
|
// Tab Bar
|
|
tabFrame := ui.NewTabFrame("Tab Frame")
|
|
tabFrame.SetBackground(render.DarkGrey)
|
|
window.Pack(tabFrame, ui.Pack{
|
|
Side: ui.N,
|
|
FillX: true,
|
|
})
|
|
|
|
// Make the tabs
|
|
cfg.makeOptionsTab(tabFrame, Width, Height)
|
|
cfg.makeControlsTab(tabFrame, Width, Height)
|
|
cfg.makeControllerTab(tabFrame, Width, Height)
|
|
cfg.makeExperimentalTab(tabFrame, Width, Height)
|
|
|
|
tabFrame.Supervise(cfg.Supervisor)
|
|
|
|
return window
|
|
}
|
|
|
|
// saveGameSettings controls pkg/usercfg to write the user settings
|
|
// to disk, based on the settings toggle-able from the UI window.
|
|
func saveGameSettings() {
|
|
log.Info("Saving game settings")
|
|
if err := usercfg.Save(); err != nil {
|
|
log.Error("Couldn't save game settings: %s", err)
|
|
}
|
|
}
|
|
|
|
// Settings Window "Options" Tab
|
|
func (c Settings) makeOptionsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.Frame {
|
|
tab := tabFrame.AddTab("Options", ui.NewLabel(ui.Label{
|
|
Text: "Options",
|
|
Font: balance.TabFont,
|
|
}))
|
|
tab.Resize(render.NewRect(Width-4, Height-tab.Size().H-46))
|
|
|
|
// Common click handler for all settings,
|
|
// so we can write the updated info to disk.
|
|
onClick := func(ed ui.EventData) error {
|
|
saveGameSettings()
|
|
return nil
|
|
}
|
|
|
|
var inputBoxWidth = 120
|
|
rows := []struct {
|
|
Header string
|
|
Text string
|
|
Boolean *bool
|
|
Integer *int
|
|
TextVariable *string
|
|
Color *render.Color
|
|
PadY int
|
|
PadX int
|
|
name string // for special cases
|
|
}{
|
|
{
|
|
Text: "Notice: all settings are temporary and controls are not editable.",
|
|
PadY: 2,
|
|
},
|
|
{
|
|
Header: "Game Options",
|
|
},
|
|
{
|
|
Boolean: c.HorizontalToolbars,
|
|
Text: "Editor: Horizontal instead of vertical toolbars",
|
|
PadX: 4,
|
|
name: "toolbars",
|
|
},
|
|
{
|
|
Boolean: c.HideTouchHints,
|
|
Text: "Hide touchscreen control hints during Play Mode",
|
|
PadX: 4,
|
|
name: "toolbars",
|
|
},
|
|
{
|
|
Boolean: c.DisableAutosave,
|
|
Text: "Disable auto-save in the Editor",
|
|
PadX: 4,
|
|
name: "autosave",
|
|
},
|
|
{
|
|
Integer: c.CrosshairSize,
|
|
Text: "Editor: Crosshair size (0 to disable):",
|
|
PadX: 4,
|
|
},
|
|
{
|
|
Color: c.CrosshairColor,
|
|
Text: "Editor: Crosshair color:",
|
|
PadX: 4,
|
|
},
|
|
{
|
|
Header: "Debug Options (temporary)",
|
|
},
|
|
{
|
|
Boolean: c.DebugOverlay,
|
|
Text: "Show debug text overlay (F3)",
|
|
PadX: 4,
|
|
},
|
|
{
|
|
Boolean: c.DebugCollision,
|
|
Text: "Show collision hitboxes (F4)",
|
|
PadX: 4,
|
|
},
|
|
{
|
|
Header: "My Custom Content",
|
|
},
|
|
{
|
|
Text: "Levels and doodads you create in-game are placed in your\n" +
|
|
"Profile Directory, which you can access below:",
|
|
},
|
|
}
|
|
for _, row := range rows {
|
|
row := row
|
|
frame := ui.NewFrame("Frame")
|
|
tab.Pack(frame, ui.Pack{
|
|
Side: ui.N,
|
|
FillX: true,
|
|
PadY: row.PadY,
|
|
})
|
|
|
|
// Headers get their own row to themselves.
|
|
if row.Header != "" {
|
|
label := ui.NewLabel(ui.Label{
|
|
Text: row.Header,
|
|
Font: balance.LabelFont,
|
|
})
|
|
frame.Pack(label, ui.Pack{
|
|
Side: ui.W,
|
|
PadX: row.PadX,
|
|
})
|
|
continue
|
|
}
|
|
|
|
// Checkboxes get their own row.
|
|
if row.Boolean != nil {
|
|
cb := ui.NewCheckbox(row.Text, row.Boolean, ui.NewLabel(ui.Label{
|
|
Text: row.Text,
|
|
Font: balance.UIFont,
|
|
}))
|
|
cb.Handle(ui.Click, onClick)
|
|
cb.Supervise(c.Supervisor)
|
|
|
|
// Add warning to the toolbars option if the EditMode is currently active.
|
|
if row.name == "toolbars" && c.SceneName == "Edit" {
|
|
ui.NewTooltip(cb, ui.Tooltip{
|
|
Text: "Note: reload your level after changing this option.\n" +
|
|
"Playtesting and returning will do.",
|
|
Edge: ui.Top,
|
|
})
|
|
}
|
|
|
|
frame.Pack(cb, ui.Pack{
|
|
Side: ui.W,
|
|
PadX: row.PadX,
|
|
})
|
|
continue
|
|
} else {
|
|
// Reserve indented space where the checkbox would have gone.
|
|
spacer := ui.NewFrame("Spacer")
|
|
spacer.Resize(render.NewRect(9, 9)) // TODO: ugly UI hack ;)
|
|
frame.Pack(spacer, ui.Pack{
|
|
Side: ui.W,
|
|
PadX: row.PadX,
|
|
})
|
|
}
|
|
|
|
// Any leftover Text gets packed to the left.
|
|
if row.Text != "" {
|
|
tf := ui.NewFrame("TextFrame")
|
|
label := ui.NewLabel(ui.Label{
|
|
Text: row.Text,
|
|
Font: balance.UIFont,
|
|
})
|
|
tf.Pack(label, ui.Pack{
|
|
Side: ui.W,
|
|
})
|
|
frame.Pack(tf, ui.Pack{
|
|
Side: ui.W,
|
|
})
|
|
}
|
|
|
|
// Int variables draw as a button to prompt for new value.
|
|
// In future: TextVariable works here too.
|
|
if row.Integer != nil {
|
|
varButton := ui.NewButton("VarButton", ui.NewLabel(ui.Label{
|
|
IntVariable: row.Integer,
|
|
Font: ui.MenuFont,
|
|
}))
|
|
varButton.Handle(ui.Click, func(ed ui.EventData) error {
|
|
shmem.Prompt(row.Text+" ", func(answer string) {
|
|
if answer == "" {
|
|
return
|
|
}
|
|
|
|
a, err := strconv.Atoi(answer)
|
|
if err != nil {
|
|
shmem.FlashError(err.Error())
|
|
return
|
|
}
|
|
|
|
if a < 0 {
|
|
a = 0
|
|
} else if a > 100 {
|
|
a = 100
|
|
}
|
|
|
|
*row.Integer = a
|
|
shmem.Flash("Crosshair size set to %d%% (WIP)", a)
|
|
|
|
// call onClick to save change to disk now
|
|
onClick(ed)
|
|
})
|
|
return nil
|
|
})
|
|
|
|
varButton.Compute(c.Engine)
|
|
varButton.Resize(render.Rect{
|
|
W: inputBoxWidth,
|
|
H: varButton.Size().H,
|
|
})
|
|
|
|
c.Supervisor.Add(varButton)
|
|
frame.Pack(varButton, ui.Pack{
|
|
Side: ui.E,
|
|
PadX: row.PadX,
|
|
})
|
|
}
|
|
|
|
// Color picker button.
|
|
if row.Color != nil {
|
|
btn := ui.NewButton("ColorBtn", ui.NewFrame(""))
|
|
style := style.DefaultButton
|
|
style.Background = *row.Color
|
|
style.HoverBackground = style.Background.Lighten(20)
|
|
btn.SetStyle(&style)
|
|
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
|
// Open a ColorPicker widget.
|
|
picker, err := ui.NewColorPicker(ui.ColorPicker{
|
|
Title: "Select a color",
|
|
Supervisor: c.Supervisor,
|
|
Engine: c.Engine,
|
|
Color: *row.Color,
|
|
OnManualInput: func(callback func(render.Color)) {
|
|
// Prompt the user to enter a hex color using the developer shell.
|
|
shmem.Prompt("New color in hex notation: ", func(answer string) {
|
|
if answer != "" {
|
|
// XXX: pure white renders as invisible, fudge it a bit.
|
|
if answer == "FFFFFF" {
|
|
answer = "FFFFFE"
|
|
}
|
|
|
|
color, err := render.HexColor(answer)
|
|
if err != nil {
|
|
shmem.Flash("Error with that color code: %s", err)
|
|
return
|
|
}
|
|
|
|
callback(color)
|
|
}
|
|
})
|
|
},
|
|
})
|
|
if err != nil {
|
|
log.Error("Couldn't open ColorPicker: %s", err)
|
|
return err
|
|
}
|
|
|
|
picker.Then(func(color render.Color) {
|
|
*row.Color = color
|
|
style.Background = color
|
|
style.HoverBackground = style.Background.Lighten(20)
|
|
|
|
// call onClick to save change to disk now
|
|
onClick(ed)
|
|
})
|
|
|
|
picker.Center(shmem.CurrentRenderEngine.WindowSize())
|
|
picker.Show()
|
|
|
|
return nil
|
|
})
|
|
|
|
btn.Compute(c.Engine)
|
|
btn.Resize(render.Rect{
|
|
W: inputBoxWidth,
|
|
H: 20, // TODO
|
|
})
|
|
|
|
c.Supervisor.Add(btn)
|
|
frame.Pack(btn, ui.Pack{
|
|
Side: ui.E,
|
|
PadX: row.PadX,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Button toolbar.
|
|
btnFrame := ui.NewFrame("Button Frame")
|
|
tab.Pack(btnFrame, ui.Pack{
|
|
Side: ui.N,
|
|
FillX: true,
|
|
PadY: 4,
|
|
})
|
|
for _, button := range []struct {
|
|
Label string
|
|
Fn func()
|
|
Style *style.Button
|
|
}{
|
|
{
|
|
Label: "Open profile directory",
|
|
Fn: func() {
|
|
path := strings.ReplaceAll(userdir.ProfileDirectory, "\\", "/")
|
|
if path[0] != '/' {
|
|
path = "/" + path
|
|
}
|
|
native.OpenURL("file://" + path)
|
|
},
|
|
Style: &balance.ButtonPrimary,
|
|
},
|
|
} {
|
|
btn := ui.NewButton(button.Label, ui.NewLabel(ui.Label{
|
|
Text: button.Label,
|
|
Font: balance.UIFont,
|
|
}))
|
|
if button.Style != nil {
|
|
btn.SetStyle(button.Style)
|
|
}
|
|
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
|
button.Fn()
|
|
return nil
|
|
})
|
|
c.Supervisor.Add(btn)
|
|
btnFrame.Pack(btn, ui.Pack{
|
|
Side: ui.W,
|
|
Expand: true,
|
|
})
|
|
}
|
|
|
|
return tab
|
|
}
|
|
|
|
// Settings Window "Controls" Tab
|
|
func (c Settings) makeControlsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.Frame {
|
|
frame := tabFrame.AddTab("Controls", ui.NewLabel(ui.Label{
|
|
Text: "Controls",
|
|
Font: balance.TabFont,
|
|
}))
|
|
frame.Resize(render.NewRect(Width-4, Height-frame.Size().H-46))
|
|
|
|
var (
|
|
halfWidth = (Width - 4) / 2 // the 4 is for window borders, TODO
|
|
shortcutTabWidth = float64(halfWidth) * 0.5
|
|
infoTabWidth = float64(halfWidth) * 0.5
|
|
rowHeight = 20
|
|
|
|
shortcutTabSize = render.NewRect(int(shortcutTabWidth), rowHeight)
|
|
infoTabSize = render.NewRect(int(infoTabWidth), rowHeight)
|
|
)
|
|
|
|
controls := []struct {
|
|
Header string
|
|
Label string
|
|
Shortcut string
|
|
}{
|
|
{
|
|
Header: "Universal Shortcut Keys",
|
|
},
|
|
{
|
|
Shortcut: "Escape",
|
|
Label: "Exit game",
|
|
},
|
|
{
|
|
Shortcut: "F1",
|
|
Label: "Guidebook",
|
|
},
|
|
{
|
|
Shortcut: "`",
|
|
Label: "Dev console",
|
|
},
|
|
{
|
|
Header: "Gameplay Controls (Play Mode)",
|
|
},
|
|
{
|
|
Shortcut: "Up or W",
|
|
Label: "Jump",
|
|
},
|
|
{
|
|
Shortcut: "Space",
|
|
Label: "Activate",
|
|
},
|
|
{
|
|
Shortcut: "Left or A",
|
|
Label: "Move left",
|
|
},
|
|
{
|
|
Shortcut: "Right or D",
|
|
Label: "Move right",
|
|
},
|
|
{
|
|
Header: "Level Editor Shortcuts",
|
|
},
|
|
{
|
|
Shortcut: "Ctrl-N",
|
|
Label: "New level",
|
|
},
|
|
{
|
|
Shortcut: "Ctrl-O",
|
|
Label: "Open drawing",
|
|
},
|
|
{
|
|
Shortcut: "Ctrl-S",
|
|
Label: "Save drawing",
|
|
},
|
|
{
|
|
Shortcut: "Shift-Ctrl-S",
|
|
Label: "Save a copy",
|
|
},
|
|
{
|
|
Shortcut: "Ctrl-Z",
|
|
Label: "Undo stroke",
|
|
},
|
|
{
|
|
Shortcut: "Ctrl-Y",
|
|
Label: "Redo stroke",
|
|
},
|
|
{
|
|
Shortcut: "P",
|
|
Label: "Playtest",
|
|
},
|
|
{
|
|
Shortcut: "0",
|
|
Label: "Scroll to origin",
|
|
},
|
|
{
|
|
Shortcut: "q",
|
|
Label: "Doodads",
|
|
},
|
|
{
|
|
Shortcut: "f",
|
|
Label: "Pencil Tool",
|
|
},
|
|
{
|
|
Shortcut: "l",
|
|
Label: "Line Tool",
|
|
},
|
|
{
|
|
Shortcut: "r",
|
|
Label: "Rectangle Tool",
|
|
},
|
|
{
|
|
Shortcut: "c",
|
|
Label: "Ellipse Tool",
|
|
},
|
|
{
|
|
Shortcut: "x",
|
|
Label: "Eraser Tool",
|
|
},
|
|
}
|
|
var curFrame = ui.NewFrame("Frame")
|
|
frame.Pack(curFrame, ui.Pack{
|
|
Side: ui.N,
|
|
FillX: true,
|
|
})
|
|
var i = -1 // manually controlled
|
|
for _, row := range controls {
|
|
i++
|
|
row := row
|
|
|
|
if row.Header != "" {
|
|
// Close out a previous Frame?
|
|
if i != 0 {
|
|
curFrame = ui.NewFrame("Header Row")
|
|
frame.Pack(curFrame, ui.Pack{
|
|
Side: ui.N,
|
|
FillX: true,
|
|
})
|
|
}
|
|
|
|
label := ui.NewLabel(ui.Label{
|
|
Text: row.Header,
|
|
Font: balance.LabelFont,
|
|
})
|
|
curFrame.Pack(label, ui.Pack{
|
|
Side: ui.W,
|
|
})
|
|
|
|
// Set up the next series of shortcut keys.
|
|
i = -1
|
|
curFrame = ui.NewFrame("Frame")
|
|
frame.Pack(curFrame, ui.Pack{
|
|
Side: ui.N,
|
|
FillX: true,
|
|
})
|
|
continue
|
|
}
|
|
|
|
// Cut a new frame every 2 items.
|
|
if i > 0 && i%2 == 0 {
|
|
curFrame = ui.NewFrame("Frame")
|
|
frame.Pack(curFrame, ui.Pack{
|
|
Side: ui.N,
|
|
FillX: true,
|
|
PadY: 2,
|
|
})
|
|
}
|
|
|
|
keyLabel := ui.NewLabel(ui.Label{
|
|
Text: row.Shortcut,
|
|
Font: balance.CodeLiteralFont,
|
|
})
|
|
keyLabel.Configure(ui.Config{
|
|
Background: render.RGBA(255, 255, 220, 255),
|
|
BorderSize: 1,
|
|
BorderStyle: ui.BorderSunken,
|
|
BorderColor: render.DarkGrey,
|
|
})
|
|
keyLabel.Resize(shortcutTabSize)
|
|
curFrame.Pack(keyLabel, ui.Pack{
|
|
Side: ui.W,
|
|
PadX: 1,
|
|
})
|
|
|
|
helpLabel := ui.NewLabel(ui.Label{
|
|
Text: row.Label,
|
|
Font: balance.UIFont,
|
|
})
|
|
helpLabel.Resize(infoTabSize)
|
|
curFrame.Pack(helpLabel, ui.Pack{
|
|
Side: ui.W,
|
|
PadX: 1,
|
|
})
|
|
}
|
|
|
|
return frame
|
|
}
|
|
|
|
// Settings Window "Experimental" Tab
|
|
func (c Settings) makeExperimentalTab(tabFrame *ui.TabFrame, Width, Height int) *ui.Frame {
|
|
tab := tabFrame.AddTab("Experimental", ui.NewLabel(ui.Label{
|
|
Text: "Experimental",
|
|
Font: balance.TabFont,
|
|
}))
|
|
tab.Resize(render.NewRect(Width-4, Height-tab.Size().H-46))
|
|
|
|
// Common click handler for all settings,
|
|
// so we can write the updated info to disk.
|
|
onClick := func(ed ui.EventData) error {
|
|
saveGameSettings()
|
|
return nil
|
|
}
|
|
|
|
rows := []struct {
|
|
Header string
|
|
Text string
|
|
Boolean *bool
|
|
TextVariable *string
|
|
PadY int
|
|
PadX int
|
|
name string // for special cases
|
|
}{
|
|
{
|
|
Header: "Enable Experimental Features",
|
|
},
|
|
{
|
|
Text: "The setting below can enable experimental features in this\n" +
|
|
"game. These are features which are still in development and\n" +
|
|
"may have unstable or buggy behavior.",
|
|
PadY: 2,
|
|
},
|
|
{
|
|
Header: "Viewport window",
|
|
},
|
|
{
|
|
Text: "This option in the Level menu opens another view into\n" +
|
|
"the level. Has glitchy wallpaper problems.",
|
|
PadY: 2,
|
|
},
|
|
{
|
|
Boolean: c.EnableFeatures,
|
|
Text: "Enable experimental features",
|
|
PadX: 4,
|
|
},
|
|
{
|
|
Text: "Restart the game for changes to take effect.",
|
|
PadY: 2,
|
|
},
|
|
}
|
|
for _, row := range rows {
|
|
row := row
|
|
frame := ui.NewFrame("Frame")
|
|
tab.Pack(frame, ui.Pack{
|
|
Side: ui.N,
|
|
FillX: true,
|
|
PadY: row.PadY,
|
|
})
|
|
|
|
// Headers get their own row to themselves.
|
|
if row.Header != "" {
|
|
label := ui.NewLabel(ui.Label{
|
|
Text: row.Header,
|
|
Font: balance.LabelFont,
|
|
})
|
|
frame.Pack(label, ui.Pack{
|
|
Side: ui.W,
|
|
PadX: row.PadX,
|
|
})
|
|
continue
|
|
}
|
|
|
|
// Checkboxes get their own row.
|
|
if row.Boolean != nil {
|
|
cb := ui.NewCheckbox(row.Text, row.Boolean, ui.NewLabel(ui.Label{
|
|
Text: row.Text,
|
|
Font: balance.UIFont,
|
|
}))
|
|
cb.Handle(ui.Click, onClick)
|
|
cb.Supervise(c.Supervisor)
|
|
|
|
// Add warning to the toolbars option if the EditMode is currently active.
|
|
if row.name == "toolbars" && c.SceneName == "Edit" {
|
|
ui.NewTooltip(cb, ui.Tooltip{
|
|
Text: "Note: reload your level after changing this option.\n" +
|
|
"Playtesting and returning will do.",
|
|
Edge: ui.Top,
|
|
})
|
|
}
|
|
|
|
frame.Pack(cb, ui.Pack{
|
|
Side: ui.W,
|
|
PadX: row.PadX,
|
|
})
|
|
continue
|
|
}
|
|
|
|
// Any leftover Text gets packed to the left.
|
|
if row.Text != "" {
|
|
tf := ui.NewFrame("TextFrame")
|
|
label := ui.NewLabel(ui.Label{
|
|
Text: row.Text,
|
|
Font: balance.UIFont,
|
|
})
|
|
tf.Pack(label, ui.Pack{
|
|
Side: ui.W,
|
|
})
|
|
frame.Pack(tf, ui.Pack{
|
|
Side: ui.W,
|
|
})
|
|
}
|
|
}
|
|
|
|
return tab
|
|
}
|
|
|
|
// Settings Window "Controller" Tab
|
|
func (c Settings) makeControllerTab(tabFrame *ui.TabFrame, Width, Height int) *ui.Frame {
|
|
tab := tabFrame.AddTab("Gamepad", ui.NewLabel(ui.Label{
|
|
Text: "Gamepad",
|
|
Font: balance.TabFont,
|
|
}))
|
|
tab.Resize(render.NewRect(Width-4, Height-tab.Size().H-46))
|
|
|
|
// Render the form.
|
|
form := magicform.Form{
|
|
Supervisor: c.Supervisor,
|
|
Engine: c.Engine,
|
|
Vertical: true,
|
|
LabelWidth: 150,
|
|
}
|
|
form.Create(tab, []magicform.Field{
|
|
{
|
|
Label: "About",
|
|
Font: balance.LabelFont,
|
|
},
|
|
{
|
|
Label: "Play Sketchy Maze with an Xbox or Nintendo controller!\n\n" +
|
|
"Full customization options aren't here yet, but you can\n" +
|
|
"choose between the 'X Style' or 'N Style' profile below.\n" +
|
|
"'N Style' will swap the A/B and X/Y buttons.",
|
|
Font: balance.UIFont,
|
|
},
|
|
{
|
|
Label: "Button Style:",
|
|
Font: balance.LabelFont,
|
|
Type: magicform.Selectbox,
|
|
Options: []magicform.Option{
|
|
{
|
|
Label: "X Style (default)",
|
|
Value: int(gamepad.XStyle),
|
|
},
|
|
{
|
|
Label: "N Style",
|
|
Value: int(gamepad.NStyle),
|
|
},
|
|
},
|
|
SelectValue: *c.ControllerStyle,
|
|
OnSelect: func(v interface{}) {
|
|
style, _ := v.(int)
|
|
log.Error("style: %d", style)
|
|
gamepad.SetStyle(gamepad.Style(style))
|
|
saveGameSettings()
|
|
},
|
|
},
|
|
{
|
|
Label: "\nThe gamepad controls vary between two modes:",
|
|
Font: balance.UIFont,
|
|
},
|
|
{
|
|
Label: "Mouse Mode (outside of gameplay)",
|
|
Font: balance.LabelFont,
|
|
},
|
|
{
|
|
Label: "The left analog stick moves a mouse cursor around.\n" +
|
|
"The right analog stick scrolls the level around.\n" +
|
|
"A or X: Left-click B or Y: Right-click\n" +
|
|
"L1: Middle-click L2: Close window",
|
|
Font: balance.UIFont,
|
|
},
|
|
{
|
|
Label: "Gameplay Mode",
|
|
Font: balance.LabelFont,
|
|
},
|
|
{
|
|
Label: "Left stick or D-Pad to move the player around.\n" +
|
|
"A or X: 'Use' B or Y: 'Jump'\n" +
|
|
"R1: Toggle between Mouse and Gameplay controls.",
|
|
Font: balance.UIFont,
|
|
},
|
|
})
|
|
|
|
return tab
|
|
}
|