Settings Window + Bugfix
* Added a Settings window for game options, such as enabling the horizontal toolbars in Edit Mode. The Settings window also has a Controls tab showing the gameplay buttons and keyboard shortcuts. * The Settings window is available as a button on the home screen OR from the Edit->Settings menu in the EditScene. * Bugfix: using WASD to move the player character now works better and is considered by the game to be identical to the arrow key inputs. Boy now updates his animation based on these keys, and they register as boolean on/off keys instead of affected by key-repeat. * Refactor the boolProps: they are all part of usercfg now, and if you run e.g. "boolProp show-all-doodads true" and then cause the user settings to save to disk, that boolProp will be permanently enabled until turned off again.
This commit is contained in:
parent
d0cfa50625
commit
864156da53
|
@ -3,7 +3,6 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
@ -11,6 +10,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/cmd/doodad/commands"
|
||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
||||
"git.kirsle.net/apps/doodle/pkg/license"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
@ -63,6 +63,7 @@ func main() {
|
|||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Error("Fatal: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
"git.kirsle.net/apps/doodle/pkg/sound"
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
"git.kirsle.net/go/render/sdl"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
|
@ -56,6 +57,11 @@ func main() {
|
|||
freeLabel = " (shareware)"
|
||||
}
|
||||
|
||||
// Load user settings from disk ASAP.
|
||||
if err := usercfg.Load(); err != nil {
|
||||
log.Error("Error loading user settings (defaults will be used): %s", err)
|
||||
}
|
||||
|
||||
app.Version = fmt.Sprintf("%s build %s%s. Built on %s",
|
||||
branding.Version,
|
||||
Build,
|
||||
|
@ -194,7 +200,9 @@ func setResolution(value string) error {
|
|||
case "mobile":
|
||||
balance.Width = 375
|
||||
balance.Height = 812
|
||||
balance.HorizontalToolbars = true
|
||||
if !usercfg.Current.Initialized {
|
||||
usercfg.Current.HorizontalToolbars = true
|
||||
}
|
||||
case "landscape":
|
||||
balance.Width = 812
|
||||
balance.Height = 375
|
||||
|
|
|
@ -5,37 +5,42 @@ import (
|
|||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
)
|
||||
|
||||
// Fun bool props to wreak havoc in the game.
|
||||
var (
|
||||
// Force show hidden doodads in the palette in Editor Mode.
|
||||
ShowHiddenDoodads bool
|
||||
/*
|
||||
Boolprop is a boolean setting that can be toggled in the game using the
|
||||
developer console. Many of these consist of usercfg settings that are
|
||||
not exposed to the Settings UI window, and secret testing functions.
|
||||
Where one points to usercfg, check usercfg.Settings for documentation
|
||||
about what that boolean does.
|
||||
*/
|
||||
type Boolprop struct {
|
||||
Name string
|
||||
Get func() bool
|
||||
Set func(bool)
|
||||
}
|
||||
|
||||
// Force ability to edit Locked levels and doodads.
|
||||
WriteLockOverride bool
|
||||
|
||||
// Pretty-print JSON files when writing.
|
||||
JSONIndent bool
|
||||
|
||||
// Temporary debug flag.
|
||||
TempDebug bool
|
||||
|
||||
// Draw horizontal toolbars in Level Editor instead of vertical.
|
||||
HorizontalToolbars bool
|
||||
)
|
||||
|
||||
// Human friendly names for the boolProps. Not necessarily the long descriptive
|
||||
// variable names above.
|
||||
var props = map[string]*bool{
|
||||
"showAllDoodads": &ShowHiddenDoodads,
|
||||
"writeLockOverride": &WriteLockOverride,
|
||||
"prettyJSON": &JSONIndent,
|
||||
"tempDebug": &TempDebug,
|
||||
"horizontalToolbars": &HorizontalToolbars,
|
||||
|
||||
// WARNING: SLOW!
|
||||
"disableChunkTextureCache": &DisableChunkTextureCache,
|
||||
// Boolprops are the map of available boolprops, shown in the dev
|
||||
// console when you type: "boolProp list"
|
||||
var Boolprops = map[string]Boolprop{
|
||||
"show-hidden-doodads": {
|
||||
Get: func() bool { return usercfg.Current.ShowHiddenDoodads },
|
||||
Set: func(v bool) { usercfg.Current.ShowHiddenDoodads = v },
|
||||
},
|
||||
"write-lock-override": {
|
||||
Get: func() bool { return usercfg.Current.WriteLockOverride },
|
||||
Set: func(v bool) { usercfg.Current.WriteLockOverride = v },
|
||||
},
|
||||
"pretty-json": {
|
||||
Get: func() bool { return usercfg.Current.JSONIndent },
|
||||
Set: func(v bool) { usercfg.Current.JSONIndent = v },
|
||||
},
|
||||
"horizontal-toolbars": {
|
||||
Get: func() bool { return usercfg.Current.HorizontalToolbars },
|
||||
Set: func(v bool) { usercfg.Current.HorizontalToolbars = v },
|
||||
},
|
||||
}
|
||||
|
||||
// GetBoolProp reads the current value of a boolProp.
|
||||
|
@ -43,25 +48,25 @@ var props = map[string]*bool{
|
|||
func GetBoolProp(name string) (bool, error) {
|
||||
if name == "list" {
|
||||
var keys []string
|
||||
for k := range props {
|
||||
for k := range Boolprops {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return false, fmt.Errorf(
|
||||
"Boolprops: %s",
|
||||
"boolprops: %s",
|
||||
strings.Join(keys, ", "),
|
||||
)
|
||||
}
|
||||
if prop, ok := props[name]; ok {
|
||||
return *prop, nil
|
||||
if prop, ok := Boolprops[name]; ok {
|
||||
return prop.Get(), nil
|
||||
}
|
||||
return false, errors.New("no such boolProp")
|
||||
}
|
||||
|
||||
// BoolProp allows easily setting a boolProp by name.
|
||||
func BoolProp(name string, v bool) error {
|
||||
if prop, ok := props[name]; ok {
|
||||
*prop = v
|
||||
if prop, ok := Boolprops[name]; ok {
|
||||
prop.Set(v)
|
||||
return nil
|
||||
}
|
||||
return errors.New("no such boolProp")
|
||||
|
|
|
@ -77,6 +77,14 @@ var (
|
|||
Color: render.Black,
|
||||
}
|
||||
|
||||
// CodeLiteralFont for rendering <code>-like text.
|
||||
CodeLiteralFont = render.Text{
|
||||
Size: 11,
|
||||
PadX: 3,
|
||||
FontFilename: "DejaVuSansMono.ttf",
|
||||
Color: render.Magenta,
|
||||
}
|
||||
|
||||
// Small font
|
||||
SmallFont = render.Text{
|
||||
Size: 10,
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/bindata"
|
||||
|
@ -34,7 +35,7 @@ func (c Command) Run(d *Doodle) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
switch c.Command {
|
||||
switch strings.ToLower(c.Command) {
|
||||
case "echo":
|
||||
d.Flash(c.ArgsLiteral)
|
||||
return nil
|
||||
|
@ -76,7 +77,7 @@ func (c Command) Run(d *Doodle) error {
|
|||
case "repl":
|
||||
d.shell.Repl = true
|
||||
d.shell.Text = "$ "
|
||||
case "boolProp":
|
||||
case "boolprop":
|
||||
return c.BoolProp(d)
|
||||
case "extract-bindata":
|
||||
// Undocumented command to extract the binary of its assets.
|
||||
|
@ -150,7 +151,7 @@ func (c Command) Help(d *Doodle) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
switch c.Args[0] {
|
||||
switch strings.ToLower(c.Args[0]) {
|
||||
case "echo":
|
||||
d.Flash("Usage: echo <message>")
|
||||
d.Flash("Flash a message back to the console")
|
||||
|
@ -183,7 +184,7 @@ func (c Command) Help(d *Doodle) error {
|
|||
d.Flash("Evaluate a line of JavaScript on the in-game interpreter")
|
||||
case "repl":
|
||||
d.Flash("Enter a JavaScript shell on the in-game interpreter")
|
||||
case "boolProp":
|
||||
case "boolprop":
|
||||
d.Flash("Toggle boolean values. `boolProp list` lists available")
|
||||
case "help":
|
||||
d.Flash("Usage: help <command>")
|
||||
|
|
|
@ -8,14 +8,14 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
)
|
||||
|
||||
// ToJSON serializes the doodad as JSON.
|
||||
func (d *Doodad) ToJSON() ([]byte, error) {
|
||||
out := bytes.NewBuffer([]byte{})
|
||||
encoder := json.NewEncoder(out)
|
||||
if balance.JSONIndent {
|
||||
if usercfg.Current.JSONIndent {
|
||||
encoder.SetIndent("", "\t")
|
||||
}
|
||||
err := encoder.Encode(d)
|
||||
|
|
|
@ -15,9 +15,12 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/native"
|
||||
"git.kirsle.net/apps/doodle/pkg/pattern"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
"git.kirsle.net/apps/doodle/pkg/windows"
|
||||
golog "git.kirsle.net/go/log"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/render/event"
|
||||
"git.kirsle.net/go/ui"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -208,13 +211,32 @@ func (d *Doodle) Run() error {
|
|||
d.TrackFPS(delay)
|
||||
|
||||
// Consume any lingering key sym.
|
||||
ev.ResetKeyDown()
|
||||
// ev.ResetKeyDown()
|
||||
}
|
||||
|
||||
log.Warn("Main Loop Exited! Shutting down...")
|
||||
return nil
|
||||
}
|
||||
|
||||
// MakeSettingsWindow initializes the windows/settings.go window
|
||||
// from anywhere you need it, binding all the variables in.
|
||||
func (d *Doodle) MakeSettingsWindow(supervisor *ui.Supervisor) *ui.Window {
|
||||
cfg := windows.Settings{
|
||||
Supervisor: supervisor,
|
||||
Engine: d.Engine,
|
||||
SceneName: d.Scene.Name(),
|
||||
OnApply: func() {
|
||||
|
||||
},
|
||||
|
||||
// Boolean checkbox bindings
|
||||
DebugOverlay: &DebugOverlay,
|
||||
DebugCollision: &DebugCollision,
|
||||
HorizontalToolbars: &usercfg.Current.HorizontalToolbars,
|
||||
}
|
||||
return windows.MakeSettingsWindow(d.width, d.height, cfg)
|
||||
}
|
||||
|
||||
// ConfirmExit may shut down Doodle gracefully after showing the user a
|
||||
// confirmation modal.
|
||||
func (d *Doodle) ConfirmExit() {
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/license"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/modal"
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
"git.kirsle.net/apps/doodle/pkg/userdir"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/render/event"
|
||||
|
@ -93,7 +94,7 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
|||
|
||||
// Write locked level?
|
||||
if s.Level != nil && s.Level.Locked {
|
||||
if balance.WriteLockOverride {
|
||||
if usercfg.Current.WriteLockOverride {
|
||||
d.Flash("Note: write lock has been overridden")
|
||||
} else {
|
||||
d.Flash("That level is write-protected and cannot be viewed in the editor.")
|
||||
|
@ -123,7 +124,7 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
|||
|
||||
// Write locked doodad?
|
||||
if s.Doodad != nil && s.Doodad.Locked {
|
||||
if balance.WriteLockOverride {
|
||||
if usercfg.Current.WriteLockOverride {
|
||||
d.Flash("Note: write lock has been overridden")
|
||||
} else {
|
||||
d.Flash("That doodad is write-protected and cannot be viewed in the editor.")
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/license"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/uix"
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/render/event"
|
||||
"git.kirsle.net/go/ui"
|
||||
|
@ -50,6 +51,7 @@ type EditorUI struct {
|
|||
publishWindow *ui.Window
|
||||
filesystemWindow *ui.Window
|
||||
licenseWindow *ui.Window
|
||||
settingsWindow *ui.Window // lazy loaded
|
||||
|
||||
// Palette window.
|
||||
Palette *ui.Window
|
||||
|
@ -158,7 +160,7 @@ func (u *EditorUI) Resized(d *Doodle) {
|
|||
|
||||
// Palette panel.
|
||||
{
|
||||
if balance.HorizontalToolbars {
|
||||
if usercfg.Current.HorizontalToolbars {
|
||||
u.Palette.Configure(ui.Config{
|
||||
Width: u.d.width,
|
||||
Height: paletteWidth,
|
||||
|
@ -191,7 +193,7 @@ func (u *EditorUI) Resized(d *Doodle) {
|
|||
Width: toolbarWidth,
|
||||
Height: innerHeight,
|
||||
}
|
||||
if balance.HorizontalToolbars {
|
||||
if usercfg.Current.HorizontalToolbars {
|
||||
tbSize.Width = innerWidth
|
||||
tbSize.Height = toolbarWidth
|
||||
}
|
||||
|
@ -207,7 +209,7 @@ func (u *EditorUI) Resized(d *Doodle) {
|
|||
{
|
||||
|
||||
frame := u.Workspace
|
||||
if balance.HorizontalToolbars {
|
||||
if usercfg.Current.HorizontalToolbars {
|
||||
frame.MoveTo(render.NewPoint(
|
||||
0,
|
||||
menuHeight+u.ToolBar.Size().H,
|
||||
|
|
|
@ -105,6 +105,13 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
|||
editMenu.AddItemAccel("Redo", "Ctrl-Y", func() {
|
||||
u.Canvas.RedoStroke()
|
||||
})
|
||||
editMenu.AddSeparator()
|
||||
editMenu.AddItem("Settings", func() {
|
||||
if u.settingsWindow == nil {
|
||||
u.settingsWindow = d.MakeSettingsWindow(u.Supervisor)
|
||||
}
|
||||
u.settingsWindow.Show()
|
||||
})
|
||||
|
||||
////////
|
||||
// Level menu
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
"git.kirsle.net/go/ui"
|
||||
)
|
||||
|
||||
|
@ -48,7 +49,7 @@ func (u *EditorUI) setupPaletteFrame(window *ui.Window) *ui.Frame {
|
|||
tooltipEdge = ui.Left
|
||||
buttonSize = 32
|
||||
)
|
||||
if balance.HorizontalToolbars {
|
||||
if usercfg.Current.HorizontalToolbars {
|
||||
packAlign = ui.W
|
||||
packConfig = ui.Pack{
|
||||
Side: packAlign,
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/drawtool"
|
||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
||||
"git.kirsle.net/apps/doodle/pkg/sprites"
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/ui"
|
||||
"git.kirsle.net/go/ui/style"
|
||||
|
@ -18,7 +19,7 @@ var toolbarSpriteSize = 32 // 32x32 sprites.
|
|||
func (u *EditorUI) SetupToolbar(d *Doodle) *ui.Frame {
|
||||
// Horizontal toolbar instead of vertical?
|
||||
var (
|
||||
isHoz = balance.HorizontalToolbars
|
||||
isHoz = usercfg.Current.HorizontalToolbars
|
||||
packAlign = ui.N
|
||||
frameSize = render.NewRect(toolbarWidth, 100)
|
||||
tooltipEdge = ui.Right
|
||||
|
|
|
@ -9,6 +9,80 @@ package keybind
|
|||
|
||||
import "git.kirsle.net/go/render/event"
|
||||
|
||||
// State returns a version of event.State which is domain specific
|
||||
// to what the game actually cares about.
|
||||
type State struct {
|
||||
State *event.State
|
||||
Shutdown bool // Escape key
|
||||
Help bool // F1
|
||||
DebugOverlay bool // F3
|
||||
DebugCollision bool // F4
|
||||
Undo bool // Ctrl-Z
|
||||
Redo bool // Ctrl-Y
|
||||
NewLevel bool // Ctrl-N
|
||||
Save bool // Ctrl-S
|
||||
SaveAs bool // Shift-Ctrl-S
|
||||
Open bool // Ctrl-O
|
||||
ZoomIn bool // +
|
||||
ZoomOut bool // -
|
||||
ZoomReset bool // 1
|
||||
Origin bool // 0
|
||||
GotoPlay bool // p
|
||||
GotoEdit bool // e
|
||||
PencilTool bool
|
||||
LineTool bool
|
||||
RectTool bool
|
||||
EllipseTool bool
|
||||
EraserTool bool
|
||||
DoodadDropper bool
|
||||
ShellKey bool
|
||||
Enter bool
|
||||
Left bool
|
||||
Right bool
|
||||
Up bool
|
||||
Down bool
|
||||
Use bool
|
||||
}
|
||||
|
||||
// FromEvent converts a render.Event readout of the current keys
|
||||
// being pressed but formats them in the way the game uses them.
|
||||
// For example, WASD and arrow keys both move the player and the
|
||||
// game only cares which direction.
|
||||
func FromEvent(ev *event.State) State {
|
||||
return State{
|
||||
State: ev,
|
||||
Shutdown: Shutdown(ev),
|
||||
Help: Help(ev),
|
||||
DebugOverlay: DebugOverlay(ev),
|
||||
DebugCollision: DebugCollision(ev), // F4
|
||||
Undo: Undo(ev), // Ctrl-Z
|
||||
Redo: Redo(ev), // Ctrl-Y
|
||||
NewLevel: NewLevel(ev), // Ctrl-N
|
||||
Save: Save(ev), // Ctrl-S
|
||||
SaveAs: SaveAs(ev), // Shift-Ctrl-S
|
||||
Open: Open(ev), // Ctrl-O
|
||||
ZoomIn: ZoomIn(ev), // +
|
||||
ZoomOut: ZoomOut(ev), // -
|
||||
ZoomReset: ZoomReset(ev), // 1
|
||||
Origin: Origin(ev), // 0
|
||||
GotoPlay: GotoPlay(ev), // p
|
||||
GotoEdit: GotoEdit(ev), // e
|
||||
PencilTool: PencilTool(ev),
|
||||
LineTool: LineTool(ev),
|
||||
RectTool: RectTool(ev),
|
||||
EllipseTool: EllipseTool(ev),
|
||||
EraserTool: EraserTool(ev),
|
||||
DoodadDropper: DoodadDropper(ev),
|
||||
ShellKey: ShellKey(ev),
|
||||
Enter: Enter(ev),
|
||||
Left: Left(ev),
|
||||
Right: Right(ev),
|
||||
Up: Up(ev),
|
||||
Down: Down(ev),
|
||||
Use: Use(ev),
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown (Escape) signals the game to start closing down.
|
||||
func Shutdown(ev *event.State) bool {
|
||||
return ev.Escape
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
)
|
||||
|
||||
// FromJSON loads a level from JSON string.
|
||||
|
@ -34,7 +34,7 @@ func FromJSON(filename string, data []byte) (*Level, error) {
|
|||
func (m *Level) ToJSON() ([]byte, error) {
|
||||
out := bytes.NewBuffer([]byte{})
|
||||
encoder := json.NewEncoder(out)
|
||||
if balance.JSONIndent {
|
||||
if usercfg.Current.JSONIndent {
|
||||
encoder.SetIndent("", "\t")
|
||||
}
|
||||
err := encoder.Encode(m)
|
||||
|
|
|
@ -34,6 +34,7 @@ type MainScene struct {
|
|||
frame *ui.Frame // Main button frame
|
||||
btnRegister *ui.Button
|
||||
winRegister *ui.Window
|
||||
winSettings *ui.Window
|
||||
|
||||
// Update check variables.
|
||||
updateButton *ui.Button
|
||||
|
@ -169,10 +170,15 @@ func (s *MainScene) Setup(d *Doodle) error {
|
|||
Name: "Edit a Level",
|
||||
Func: d.GotoLoadMenu,
|
||||
},
|
||||
// {
|
||||
// Name: "Settings",
|
||||
// Func: d.GotoSettingsMenu,
|
||||
// },
|
||||
{
|
||||
Name: "Settings",
|
||||
Func: func() {
|
||||
if s.winSettings == nil {
|
||||
s.winSettings = d.MakeSettingsWindow(s.Supervisor)
|
||||
}
|
||||
s.winSettings.Show()
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, button := range buttons {
|
||||
button := button
|
||||
|
@ -345,13 +351,20 @@ func (s *MainScene) Draw(d *Doodle) error {
|
|||
s.canvas.Present(d.Engine, render.Origin)
|
||||
|
||||
// Draw a sheen over the level for clarity.
|
||||
d.Engine.DrawBox(render.RGBA(255, 255, 254, 128), render.Rect{
|
||||
d.Engine.DrawBox(render.RGBA(255, 255, 254, 96), render.Rect{
|
||||
X: 0,
|
||||
Y: 0,
|
||||
W: d.width,
|
||||
H: d.height,
|
||||
})
|
||||
|
||||
// Draw out bounding boxes.
|
||||
if DebugCollision {
|
||||
for _, actor := range s.canvas.Actors() {
|
||||
d.DrawCollisionBox(s.canvas, actor)
|
||||
}
|
||||
}
|
||||
|
||||
// App title label.
|
||||
s.labelTitle.MoveTo(render.Point{
|
||||
X: (d.width / 2) - (s.labelTitle.Size().W / 2),
|
||||
|
|
|
@ -551,7 +551,7 @@ func (s *PlayScene) movePlayer(ev *event.State) {
|
|||
// If the "Use" key is pressed, set an actor flag on the player.
|
||||
s.Player.SetUsing(keybind.Use(ev))
|
||||
|
||||
s.scripting.To(s.Player.ID()).Events.RunKeypress(ev)
|
||||
s.scripting.To(s.Player.ID()).Events.RunKeypress(keybind.FromEvent(ev))
|
||||
}
|
||||
|
||||
// Drawing returns the private world drawing, for debugging with the console.
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"errors"
|
||||
"sync"
|
||||
|
||||
"git.kirsle.net/go/render/event"
|
||||
"git.kirsle.net/apps/doodle/pkg/keybind"
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
|
@ -73,7 +73,7 @@ func (e *Events) OnKeypress(call otto.FunctionCall) otto.Value {
|
|||
}
|
||||
|
||||
// RunKeypress invokes the OnCollide handler function.
|
||||
func (e *Events) RunKeypress(ev *event.State) error {
|
||||
func (e *Events) RunKeypress(ev keybind.State) error {
|
||||
return e.run(KeypressEvent, ev)
|
||||
}
|
||||
|
||||
|
|
97
pkg/usercfg/usercfg.go
Normal file
97
pkg/usercfg/usercfg.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
Package usercfg has functions around the user's Game Settings.
|
||||
|
||||
Other places in the codebase to look for its related functionality:
|
||||
|
||||
- pkg/windows/settings.go: the Settings Window is the UI owner of
|
||||
this feature, it adjusts the usercfg.Current struct and Saves the
|
||||
changes to disk.
|
||||
*/
|
||||
package usercfg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/userdir"
|
||||
)
|
||||
|
||||
// Settings are the available game settings.
|
||||
type Settings struct {
|
||||
// Initialized is set true the first time the settings are saved to
|
||||
// disk, so the game may decide some default settings for first-time
|
||||
// user experience, e.g. set horizontal toolbars for mobile.
|
||||
Initialized bool
|
||||
|
||||
// Configurable settings (pkg/windows/settings.go)
|
||||
HorizontalToolbars bool `json:",omitempty"`
|
||||
|
||||
// Secret boolprops from balance/boolprops.go
|
||||
ShowHiddenDoodads bool `json:",omitempty"`
|
||||
WriteLockOverride bool `json:",omitempty"`
|
||||
JSONIndent bool `json:",omitempty"`
|
||||
|
||||
// Bookkeeping.
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// Current loaded settings, good defaults by default.
|
||||
var Current = Defaults()
|
||||
|
||||
// Defaults returns sensible default user settings.
|
||||
func Defaults() *Settings {
|
||||
return &Settings{}
|
||||
}
|
||||
|
||||
// Filepath returns the path to the settings file.
|
||||
func Filepath() string {
|
||||
return filepath.Join(userdir.ProfileDirectory, "settings.json")
|
||||
}
|
||||
|
||||
// Save the settings to disk.
|
||||
func Save() error {
|
||||
var (
|
||||
filename = Filepath()
|
||||
bin = bytes.NewBuffer([]byte{})
|
||||
enc = json.NewEncoder(bin)
|
||||
)
|
||||
enc.SetIndent("", "\t")
|
||||
Current.Initialized = true
|
||||
Current.UpdatedAt = time.Now()
|
||||
if err := enc.Encode(Current); err != nil {
|
||||
return err
|
||||
}
|
||||
err := ioutil.WriteFile(filename, bin.Bytes(), 0644)
|
||||
return err
|
||||
}
|
||||
|
||||
// Load the settings from disk. The loaded settings will be available
|
||||
// at usercfg.Current.
|
||||
func Load() error {
|
||||
var (
|
||||
filename = Filepath()
|
||||
settings = Defaults()
|
||||
)
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
return nil // no file, no problem
|
||||
}
|
||||
|
||||
fh, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode JSON from file.
|
||||
dec := json.NewDecoder(fh)
|
||||
err = dec.Decode(settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Current = settings
|
||||
return nil
|
||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/uix"
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/ui"
|
||||
)
|
||||
|
@ -81,7 +82,7 @@ func NewDoodadDropper(config DoodadDropper) *ui.Window {
|
|||
}
|
||||
|
||||
// Skip hidden doodads.
|
||||
if doodad.Hidden && !balance.ShowHiddenDoodads {
|
||||
if doodad.Hidden && !usercfg.Current.ShowHiddenDoodads {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/pattern"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/ui"
|
||||
"git.kirsle.net/go/ui/style"
|
||||
|
@ -226,7 +227,7 @@ func NewPaletteEditor(config PaletteEditor) *ui.Window {
|
|||
})
|
||||
|
||||
for _, t := range pattern.Builtins {
|
||||
if t.Hidden && !balance.ShowHiddenDoodads {
|
||||
if t.Hidden && !usercfg.Current.ShowHiddenDoodads {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/native"
|
||||
"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.
|
||||
|
@ -15,80 +16,478 @@ type Settings struct {
|
|||
// Settings passed in by doodle
|
||||
Supervisor *ui.Supervisor
|
||||
Engine render.Engine
|
||||
|
||||
// Boolean bindings.
|
||||
DebugOverlay *bool
|
||||
DebugCollision *bool
|
||||
HorizontalToolbars *bool
|
||||
|
||||
// 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 {
|
||||
window := ui.NewWindow("Game Settings")
|
||||
var (
|
||||
Width = 400
|
||||
Height = 400
|
||||
ActiveTab = "index"
|
||||
|
||||
// The tab frames
|
||||
TabOptions *ui.Frame // index
|
||||
TabControls *ui.Frame // controls
|
||||
)
|
||||
if cfg.ActiveTab != "" {
|
||||
ActiveTab = cfg.ActiveTab
|
||||
}
|
||||
|
||||
window := ui.NewWindow("Settings")
|
||||
window.SetButtons(ui.CloseButton)
|
||||
window.Configure(ui.Config{
|
||||
Width: 400,
|
||||
Height: 170,
|
||||
Width: Width,
|
||||
Height: Height,
|
||||
Background: render.Grey,
|
||||
})
|
||||
|
||||
// {
|
||||
// row := ui.NewFrame("Theme Frame")
|
||||
// label := ui.NewLabel(ui.Label{
|
||||
// Text: "Theme:",
|
||||
// Font: balance.MenuFont,
|
||||
// })
|
||||
// }
|
||||
|
||||
text := ui.NewLabel(ui.Label{
|
||||
Text: fmt.Sprintf("%s is a drawing-based maze game.\n\n"+
|
||||
"Copyright © %s.\nAll rights reserved.\n\n"+
|
||||
"Version %s",
|
||||
branding.AppName,
|
||||
branding.Copyright,
|
||||
branding.Version,
|
||||
),
|
||||
///////////
|
||||
// Tab Bar
|
||||
tabFrame := ui.NewFrame("Tab Bar")
|
||||
tabFrame.SetBackground(render.DarkGrey)
|
||||
window.Pack(tabFrame, ui.Pack{
|
||||
Side: ui.N,
|
||||
FillX: true,
|
||||
})
|
||||
window.Pack(text, ui.Pack{
|
||||
Side: ui.N,
|
||||
Padding: 8,
|
||||
})
|
||||
|
||||
frame := ui.NewFrame("Button frame")
|
||||
buttons := []struct {
|
||||
for _, tab := range []struct {
|
||||
label string
|
||||
f func()
|
||||
value string
|
||||
}{
|
||||
{"Website", func() {
|
||||
native.OpenURL(branding.Website)
|
||||
}},
|
||||
{"Open Source Licenses", func() {
|
||||
// TODO: open file
|
||||
native.OpenURL("./Open Source Licenses.md")
|
||||
}},
|
||||
}
|
||||
for _, button := range buttons {
|
||||
button := button
|
||||
|
||||
btn := ui.NewButton(button.label, ui.NewLabel(ui.Label{
|
||||
Text: button.label,
|
||||
Font: balance.MenuFont,
|
||||
{"Options", "index"},
|
||||
{"Controls", "controls"},
|
||||
} {
|
||||
radio := ui.NewRadioButton(tab.label, &ActiveTab, tab.value, ui.NewLabel(ui.Label{
|
||||
Text: tab.label,
|
||||
Font: balance.UIFont,
|
||||
}))
|
||||
|
||||
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
button.f()
|
||||
radio.SetStyle(&balance.ButtonBabyBlue)
|
||||
radio.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
switch ActiveTab {
|
||||
case "index":
|
||||
TabOptions.Show()
|
||||
TabControls.Hide()
|
||||
case "controls":
|
||||
TabOptions.Hide()
|
||||
TabControls.Show()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
btn.Compute(cfg.Engine)
|
||||
cfg.Supervisor.Add(btn)
|
||||
|
||||
frame.Pack(btn, ui.Pack{
|
||||
cfg.Supervisor.Add(radio)
|
||||
tabFrame.Pack(radio, ui.Pack{
|
||||
Side: ui.W,
|
||||
PadX: 4,
|
||||
Expand: true,
|
||||
Fill: true,
|
||||
})
|
||||
}
|
||||
window.Pack(frame, ui.Pack{
|
||||
Side: ui.N,
|
||||
Padding: 8,
|
||||
|
||||
///////////
|
||||
// Options (index) Tab
|
||||
TabOptions = cfg.makeOptionsTab(Width, Height)
|
||||
if ActiveTab != "index" {
|
||||
TabOptions.Hide()
|
||||
}
|
||||
window.Pack(TabOptions, ui.Pack{
|
||||
Side: ui.N,
|
||||
FillX: true,
|
||||
})
|
||||
|
||||
///////////
|
||||
// Controls Tab
|
||||
TabControls = cfg.makeControlsTab(Width, Height)
|
||||
if ActiveTab != "controls" {
|
||||
TabControls.Hide()
|
||||
}
|
||||
window.Pack(TabControls, ui.Pack{
|
||||
Side: ui.N,
|
||||
FillX: true,
|
||||
})
|
||||
|
||||
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(Width, Height int) *ui.Frame {
|
||||
tab := ui.NewFrame("Options Tab")
|
||||
|
||||
log.Error("c.Super: %+v", c.Supervisor)
|
||||
|
||||
// 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
|
||||
}{
|
||||
{
|
||||
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",
|
||||
},
|
||||
{
|
||||
Header: "Debug Options",
|
||||
},
|
||||
{
|
||||
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. This is also where you can place content made\n" +
|
||||
"by others to use them in your game. Click on the button below\n" +
|
||||
"to (hopefully) be taken to your Profile Directory:",
|
||||
},
|
||||
}
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
native.OpenURL("file://" + userdir.ProfileDirectory)
|
||||
},
|
||||
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(Width, Height int) *ui.Frame {
|
||||
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)
|
||||
)
|
||||
frame := ui.NewFrame("Controls Tab")
|
||||
|
||||
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: "d",
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user