Added support for changing controls.

Changed info screen slightly.
This commit is contained in:
Sander Schobers 2021-08-10 22:36:42 +02:00
parent 165d1fcd26
commit 000348339d
9 changed files with 432 additions and 31 deletions

View File

@ -1,7 +1,7 @@
- [X] Increase difficulty. - [X] Increase difficulty.
- [ ] Add music & sounds. - [ ] Add music & sounds.
- [ ] Keep score/difficulty level (resume & restart). - [ ] Keep score/difficulty level (resume & restart).
- [ ] Explain controls on info page. - [X] ~~Explain controls on info page~~ add settings for controls.
- [X] Fix usage of go/embed (and remove rice again). - [X] Fix usage of go/embed (and remove rice again).
- [X] Add monster animations (~~jumping on tile &~~ towards new tile). - [X] Add monster animations (~~jumping on tile &~~ towards new tile).
- [ ] Scale icons (heart & star on right side) when playing. - [ ] Scale icons (heart & star on right side) when playing.

View File

@ -29,7 +29,7 @@ func NewAnimatedTexture(texture NamedTexture, n int) AnimatedTexture {
func FitAnimatedTexture(texture NamedTexture, scale float32, n int) AnimatedTexture { func FitAnimatedTexture(texture NamedTexture, scale float32, n int) AnimatedTexture {
height := float32(texture.Texture().Height()) height := float32(texture.Texture().Height())
scale = geom.Round32(height*scale) / height // clip scale to integer width/height scale = ScaleRound(height, scale)
return newAnimatedTexture(texture, texture.Scaled(scale), n) return newAnimatedTexture(texture, texture.Scaled(scale), n)
} }
@ -44,3 +44,11 @@ func (t AnimatedTexture) Frames() int { return len(t.frames) }
func (t AnimatedTexture) Scale(scale float32) AnimatedTexture { func (t AnimatedTexture) Scale(scale float32) AnimatedTexture {
return FitAnimatedTexture(t.texture, scale, t.Frames()) return FitAnimatedTexture(t.texture, scale, t.Frames())
} }
func FindScaleRound(length, desired float32) float32 {
return ScaleRound(length, desired/length)
}
func ScaleRound(length, scale float32) float32 {
return geom.Round32(length*scale) / length
}

View File

@ -55,7 +55,7 @@ func (a *app) Init(ctx ui.Context) error {
ctx.Overlays().AddOnTop(fpsOverlayName, &play.FPS{Align: ui.AlignRight}, false) ctx.Overlays().AddOnTop(fpsOverlayName, &play.FPS{Align: ui.AlignRight}, false)
a.context = newAppContext(ctx, func(control ui.Control) { a.context = newAppContext(ctx, a.settings, func(control ui.Control) {
a.Content = control a.Content = control
}) })
a.context.ShowMainMenu(ctx) a.context.ShowMainMenu(ctx)

View File

@ -8,7 +8,8 @@ import (
type appContext struct { type appContext struct {
setView func(ui.Control) setView func(ui.Control)
Debug bool Settings *tins2021.Settings
Debug bool
StarTexture tins2021.AnimatedTexture StarTexture tins2021.AnimatedTexture
HeartTexture tins2021.AnimatedTexture HeartTexture tins2021.AnimatedTexture
@ -16,10 +17,11 @@ type appContext struct {
MonsterTextures map[tins2021.MonsterType]tins2021.AnimatedTexture MonsterTextures map[tins2021.MonsterType]tins2021.AnimatedTexture
} }
func newAppContext(ctx ui.Context, setView func(ui.Control)) *appContext { func newAppContext(ctx ui.Context, settings *tins2021.Settings, setView func(ui.Control)) *appContext {
textures := textureGenerator{} textures := textureGenerator{}
app := &appContext{ app := &appContext{
setView: setView, setView: setView,
Settings: settings,
StarTexture: newAnimatedTexture(ctx, "star", defaultAnimationFrames, textures.Star), StarTexture: newAnimatedTexture(ctx, "star", defaultAnimationFrames, textures.Star),
HeartTexture: newAnimatedTexture(ctx, "heart", defaultAnimationFrames, textures.Heart), HeartTexture: newAnimatedTexture(ctx, "heart", defaultAnimationFrames, textures.Heart),
MonsterTextures: map[tins2021.MonsterType]tins2021.AnimatedTexture{ MonsterTextures: map[tins2021.MonsterType]tins2021.AnimatedTexture{
@ -62,6 +64,10 @@ func (app *appContext) ShowCredits(ctx ui.Context) {
app.setView(newCredits(app, ctx)) app.setView(newCredits(app, ctx))
} }
func (app *appContext) ShowSettings(ctx ui.Context) {
app.setView(newSettings(app, ctx))
}
func (app *appContext) ShowInfo(ctx ui.Context) { func (app *appContext) ShowInfo(ctx ui.Context) {
app.setView(newInfo(app, ctx)) app.setView(newInfo(app, ctx))
} }

View File

@ -6,14 +6,19 @@ import (
"opslag.de/schobers/zntg/ui" "opslag.de/schobers/zntg/ui"
) )
const infoLegendIconSize = .36
const infoLegendIconMargin = 4.
const infoLegendSeparatorMargin = 24
const infoText = "Qbitter is a game loosly based on the work \"LW305 Kringloop\" of M.C. Escher where a gnome runs down a stairs and morphs into its 2D abstract shape. The game also lends ideas from Q*Bert, a game from the eighties that is based on the works of M.C. Escher. \n\nIn the game you (represented as a gnome) have to collect stars while trying to avoid enemies (hexagons). Every level has increasing difficulty."
var infoLegendIconRectangle = geom.RectRelF32(0, 0, infoLegendIconSize*tins2021.TextureSize, infoLegendIconSize*tins2021.TextureSize)
type info struct { type info struct {
ui.StackPanel ui.StackPanel
app *appContext app *appContext
} }
const infoText = "Qbitter is a game loosly based on the work \"LW305 Kringloop\" of M.C. Escher where a gnome runs down a stairs and morphs into its 2D abstract shape. The game also lends ideas from Q*Bert, a game from the eighties that is based on the works of M.C. Escher. \n\nIn the game you (represented as a gnome) have to collect stars while trying to avoid enemies (hexagons). Every level has increasing difficulty."
type infoLegend struct { type infoLegend struct {
ui.ControlBase ui.ControlBase
@ -22,44 +27,42 @@ type infoLegend struct {
} }
func (l *infoLegend) DesiredSize(ctx ui.Context, size geom.PointF32) geom.PointF32 { func (l *infoLegend) DesiredSize(ctx ui.Context, size geom.PointF32) geom.PointF32 {
return geom.PtF32(geom.NaN32(), .4*tins2021.TextureSize) textureHeight := float32(l.Icon.Height())
font := ctx.Fonts().Font("score")
fontHeight := float32(font.Height())
return geom.PtF32(textureHeight+infoLegendSeparatorMargin+font.WidthOf(l.Description), geom.Max32(textureHeight+2*infoLegendIconMargin, fontHeight))
} }
const infoLegenIconSize = .36
var infoLegendIconRectangle = geom.RectRelF32(0, 0, infoLegenIconSize*tins2021.TextureSize, infoLegenIconSize*tins2021.TextureSize)
func (l *infoLegend) Render(ctx ui.Context) { func (l *infoLegend) Render(ctx ui.Context) {
bounds := l.Bounds() bounds := l.Bounds()
separator := .3 * bounds.Dx()
textureHeight := float32(l.Icon.Height()) textureHeight := float32(l.Icon.Height())
renderer := ctx.Renderer() renderer := ctx.Renderer()
renderer.DrawTexturePointOptions(l.Icon, bounds.Min.Add2D(separator-textureHeight, 0), ui.DrawOptions{Source: &infoLegendIconRectangle}) renderer.DrawTexturePointOptions(l.Icon, bounds.Min, ui.DrawOptions{Source: &infoLegendIconRectangle})
font := ctx.Fonts().Font("score") font := ctx.Fonts().Font("score")
fontHeight := float32(font.Height()) fontHeight := float32(font.Height())
renderer.Text(font, bounds.Min.Add2D(separator+12, .5*(textureHeight-fontHeight)), ctx.Style().Palette.Text, l.Description) renderer.Text(font, bounds.Min.Add2D(textureHeight+infoLegendSeparatorMargin, .5*(textureHeight-fontHeight)+infoLegendIconMargin), ctx.Style().Palette.Text, l.Description)
} }
func newInfo(app *appContext, ctx ui.Context) ui.Control { func newInfo(app *appContext, ctx ui.Context) ui.Control {
legend := ui.BuildStackPanel(ui.OrientationVertical, func(p *ui.StackPanel) { legend := ui.BuildStackPanel(ui.OrientationVertical, func(p *ui.StackPanel) {
p.AddChild(&infoLegend{ p.AddChild(&infoLegend{
Icon: ctx.Textures().ScaledByName("star", infoLegenIconSize), Icon: ctx.Textures().ScaledByName("star", infoLegendIconSize),
Description: "Collect them to complete the level.", Description: "Collect them to complete the level.",
}) })
p.AddChild(&infoLegend{ p.AddChild(&infoLegend{
Icon: ctx.Textures().ScaledByName("heart", infoLegenIconSize), Icon: ctx.Textures().ScaledByName("heart", infoLegendIconSize),
Description: "Gives (back) a life.", Description: "Gives (back) a life.",
}) })
p.AddChild(&infoLegend{ p.AddChild(&infoLegend{
Icon: ctx.Textures().ScaledByName("straight-walking-monster", infoLegenIconSize), Icon: ctx.Textures().ScaledByName("straight-walking-monster", infoLegendIconSize),
Description: "Monster that walks over a fixed diagonal.", Description: "Monster that walks over a fixed diagonal.",
}) })
p.AddChild(&infoLegend{ p.AddChild(&infoLegend{
Icon: ctx.Textures().ScaledByName("random-walking-monster", infoLegenIconSize), Icon: ctx.Textures().ScaledByName("random-walking-monster", infoLegendIconSize),
Description: "Monster that walks randomly.", Description: "Monster that walks randomly.",
}) })
p.AddChild(&infoLegend{ p.AddChild(&infoLegend{
Icon: ctx.Textures().ScaledByName("chasing-monster", infoLegenIconSize), Icon: ctx.Textures().ScaledByName("chasing-monster", infoLegendIconSize),
Description: "Monster that walks towards you.", Description: "Monster that walks towards you.",
}) })
}) })
@ -70,7 +73,7 @@ func newInfo(app *appContext, ctx ui.Context) ui.Control {
label("", "score"), // spacing label("", "score"), // spacing
paragraphOpts(infoText, "score", labelOptions{TextAlignment: ui.AlignCenter}), paragraphOpts(infoText, "score", labelOptions{TextAlignment: ui.AlignCenter}),
label("", "score"), // spacing label("", "score"), // spacing
legend, Center(legend),
}, },
}, },
Orientation: ui.OrientationVertical, Orientation: ui.OrientationVertical,

View File

@ -28,6 +28,8 @@ type levelController struct {
MovingMonsters *tins2021.Animations MovingMonsters *tins2021.Animations
SmallFont *tins2021.BitmapFont SmallFont *tins2021.BitmapFont
Controls map[ui.Key]tins2021.Direction
} }
func newLevelControl(app *appContext, ctx ui.Context, level *tins2021.Level) *levelController { func newLevelControl(app *appContext, ctx ui.Context, level *tins2021.Level) *levelController {
@ -41,6 +43,37 @@ func newLevelControl(app *appContext, ctx ui.Context, level *tins2021.Level) *le
panic(err) panic(err)
} }
control.SmallFont = small control.SmallFont = small
switch app.Settings.Controls.Type {
case controlsTypeArrows:
control.Controls = map[ui.Key]tins2021.Direction{
ui.KeyUp: tins2021.DirectionUpLeft,
ui.KeyLeft: tins2021.DirectionDownLeft,
ui.KeyDown: tins2021.DirectionDownRight,
ui.KeyRight: tins2021.DirectionUpRight,
}
case controlsTypeCustom:
find := func(s string, defaultKey ui.Key) ui.Key {
for key, setting := range supportedCustomKeys {
if s == setting {
return key
}
}
return defaultKey
}
control.Controls = map[ui.Key]tins2021.Direction{
find(app.Settings.Controls.MoveUpLeft, ui.KeyW): tins2021.DirectionUpLeft,
find(app.Settings.Controls.MoveDownLeft, ui.KeyA): tins2021.DirectionDownLeft,
find(app.Settings.Controls.MoveDownRight, ui.KeyS): tins2021.DirectionDownRight,
find(app.Settings.Controls.MoveUpRight, ui.KeyD): tins2021.DirectionUpRight,
}
default:
control.Controls = map[ui.Key]tins2021.Direction{
ui.KeyW: tins2021.DirectionUpLeft,
ui.KeyA: tins2021.DirectionDownLeft,
ui.KeyS: tins2021.DirectionDownRight,
ui.KeyD: tins2021.DirectionUpRight,
}
}
control.Play(level) control.Play(level)
@ -77,15 +110,9 @@ func (r *levelController) Handle(ctx ui.Context, e ui.Event) bool {
switch e := e.(type) { switch e := e.(type) {
case *ui.KeyDownEvent: case *ui.KeyDownEvent:
switch e.Key { dir, ok := r.Controls[e.Key]
case ui.KeyW: if ok {
r.Level.MovePlayer(tins2021.DirectionUpLeft) r.Level.MovePlayer(dir)
case ui.KeyD:
r.Level.MovePlayer(tins2021.DirectionUpRight)
case ui.KeyS:
r.Level.MovePlayer(tins2021.DirectionDownRight)
case ui.KeyA:
r.Level.MovePlayer(tins2021.DirectionDownLeft)
} }
} }

View File

@ -37,6 +37,7 @@ func newMainMenu(app *appContext, ctx ui.Context) ui.Control {
menu.Add("Play", func(ctx ui.Context) { menu.Add("Play", func(ctx ui.Context) {
app.Play(ctx) app.Play(ctx)
}) })
menu.Add("Controls", func(ctx ui.Context) { app.ShowSettings(ctx) })
menu.Add("Credits", func(ctx ui.Context) { app.ShowCredits(ctx) }) menu.Add("Credits", func(ctx ui.Context) { app.ShowCredits(ctx) })
menu.Add("Quit", func(ctx ui.Context) { ctx.Quit() }) menu.Add("Quit", func(ctx ui.Context) { ctx.Quit() })

347
cmd/tins2021/settings.go Normal file
View File

@ -0,0 +1,347 @@
package main
import (
"fmt"
"image"
"image/color"
"io/ioutil"
"github.com/golang/freetype/truetype"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dimg"
"opslag.de/schobers/geom"
"opslag.de/schobers/tins2021"
"opslag.de/schobers/zntg"
"opslag.de/schobers/zntg/ui"
)
const keyboardKeyCornerRadius = .1 * keyboardKeyWidth
const keyboardKeyHeight = .2 * keyboardLayoutTextureWidth
const keyboardKeySkew = .15
const keyboardKeyWidth = .25 * keyboardLayoutTextureWidth
const keyboardLayoutTextureHeight = 256
const keyboardLayoutTextureWidth = 2 * keyboardLayoutTextureHeight
func drawKey(ctx *draw2dimg.GraphicContext, font *truetype.Font, center geom.PointF, key rune, color color.Color) {
const cornerRadius = keyboardKeyCornerRadius
const keyHeight_5 = .5 * keyboardKeyHeight
const keyWidth_5 = .5 * keyboardKeyWidth
skewed := func(x, y float64) (float64, float64) {
x, y = skewedKeyboardCoordinates(x, y)
return center.X + x, center.Y + y
}
corner := func(x, y, start float64) {
for a := start; a <= start+.25; a += .025 {
aa := a * 2 * geom.Pi
ctx.LineTo(skewed(x+cornerRadius*geom.Cos(aa), y-cornerRadius*geom.Sin(aa)))
}
}
ctx.SetLineWidth(3)
ctx.SetStrokeColor(color)
ctx.SetFillColor(color)
ctx.MoveTo(skewed(-keyWidth_5+cornerRadius, keyHeight_5))
ctx.LineTo(skewed(keyWidth_5-cornerRadius, keyHeight_5))
corner(keyWidth_5-cornerRadius, keyHeight_5-cornerRadius, .75)
ctx.LineTo(skewed(keyWidth_5, -keyHeight_5+cornerRadius))
corner(keyWidth_5-cornerRadius, -keyHeight_5+cornerRadius, 0)
ctx.LineTo(skewed(-keyWidth_5+cornerRadius, -keyHeight_5))
corner(-keyWidth_5+cornerRadius, -keyHeight_5+cornerRadius, .25)
ctx.LineTo(skewed(-keyWidth_5, keyHeight_5-cornerRadius))
corner(-keyWidth_5+cornerRadius, keyHeight_5-cornerRadius, .5)
ctx.Close()
ctx.Stroke()
ctx.FontCache = fontCache{font}
ctx.SetFont(font)
ctx.SetFontSize(keyHeight_5)
text := fmt.Sprintf("%c", key)
textLeft, textTop, textRight, textBottom := ctx.GetStringBounds(text)
textX, textY := skewed(-.5*(textRight-textLeft), .5*(textBottom-textTop))
ctx.FillStringAt(text, textX, textY)
}
type fontCache struct{ *truetype.Font }
func (f fontCache) Load(draw2d.FontData) (*truetype.Font, error) { return f.Font, nil }
func (fontCache) Store(draw2d.FontData, *truetype.Font) {}
func generateArrowKeys(resources ui.Resources) image.Image {
return generateKeys(resources,
keyboardLayoutKey{Position: geom.PtF(.53, .25), Key: '↑'},
keyboardLayoutKey{Position: geom.PtF(.2, .75), Key: '←'},
keyboardLayoutKey{Position: geom.PtF(.5, .75), Key: '↓'},
keyboardLayoutKey{Position: geom.PtF(.8, .75), Key: '→'},
)
}
func generateArrowKeysHighlight(resources ui.Resources, highlight [4]bool) image.Image {
spaceOrRune := func(r rune, space bool) rune {
if space {
return ' '
}
return r
}
return generateKeys(resources,
keyboardLayoutKey{Position: geom.PtF(.45, .25), Key: spaceOrRune('↑', !highlight[0]), Highlight: highlight[0]},
keyboardLayoutKey{Position: geom.PtF(.2, .75), Key: spaceOrRune('←', !highlight[1]), Highlight: highlight[1]},
keyboardLayoutKey{Position: geom.PtF(.5, .75), Key: spaceOrRune('↓', !highlight[2]), Highlight: highlight[2]},
keyboardLayoutKey{Position: geom.PtF(.8, .75), Key: spaceOrRune('→', !highlight[3]), Highlight: highlight[3]},
)
}
func generateCustomKeys(resources ui.Resources, keys [4]rune) image.Image {
return generateKeys(resources,
keyboardLayoutKey{Position: geom.PtF(.45, .25), Key: keys[0]},
keyboardLayoutKey{Position: geom.PtF(.2, .75), Key: keys[1]},
keyboardLayoutKey{Position: geom.PtF(.5, .75), Key: keys[2]},
keyboardLayoutKey{Position: geom.PtF(.8, .75), Key: keys[3]},
)
}
func generateKeys(resources ui.Resources, keys ...keyboardLayoutKey) image.Image {
im := image.NewRGBA(image.Rect(0, 0, keyboardLayoutTextureWidth, keyboardLayoutTextureHeight))
ctx := draw2dimg.NewGraphicContext(im)
font, err := parseFont(resources)
if err != nil {
panic(err)
}
for _, key := range keys {
var color color.Color = color.White
if key.Highlight {
color = zntg.MustHexColor(tins2021.Orange)
}
center := geom.PtF(key.Position.X*keyboardLayoutTextureWidth, key.Position.Y*keyboardLayoutTextureHeight)
drawKey(ctx, font, center, key.Key, color)
}
return im
}
func generateWASDKeys(resources ui.Resources) image.Image {
return generateKeys(resources,
keyboardLayoutKey{Position: geom.PtF(.45, .25), Key: 'W'},
keyboardLayoutKey{Position: geom.PtF(.2, .75), Key: 'A'},
keyboardLayoutKey{Position: geom.PtF(.5, .75), Key: 'S'},
keyboardLayoutKey{Position: geom.PtF(.8, .75), Key: 'D'},
)
}
type keyboardLayoutKey struct {
Position geom.PointF
Key rune
Highlight bool
}
func parseFont(resources ui.Resources) (*truetype.Font, error) {
ttf, err := resources.OpenResource("resources/fonts/FiraMono-Regular.ttf")
if err != nil {
return nil, err
}
defer ttf.Close()
data, err := ioutil.ReadAll(ttf)
if err != nil {
return nil, err
}
return truetype.Parse(data)
}
type settings struct {
ui.StackPanel
app *appContext
ActiveLayout int
SelectedLayout int
SelectingCustom int
}
const (
controlsTypeWASD = "wasd"
controlsTypeArrows = "arrows"
controlsTypeCustom = "custom"
)
func newSettings(app *appContext, ctx ui.Context) *settings {
ctx.Textures().CreateTextureGo("layout-wasd", generateWASDKeys(ctx.Resources()), true)
ctx.Textures().CreateTextureGo("layout-arrows", generateArrowKeys(ctx.Resources()), true)
ctx.Textures().CreateTextureGo("layout-select-1", generateArrowKeysHighlight(ctx.Resources(), [4]bool{true, false, false, false}), true)
ctx.Textures().CreateTextureGo("layout-select-2", generateArrowKeysHighlight(ctx.Resources(), [4]bool{false, true, false, false}), true)
ctx.Textures().CreateTextureGo("layout-select-3", generateArrowKeysHighlight(ctx.Resources(), [4]bool{false, false, true, false}), true)
ctx.Textures().CreateTextureGo("layout-select-4", generateArrowKeysHighlight(ctx.Resources(), [4]bool{false, false, false, true}), true)
var layout int
switch app.Settings.Controls.Type {
case controlsTypeArrows:
layout = 1
case controlsTypeCustom:
layout = 2
}
settings := &settings{app: app, ActiveLayout: layout, SelectedLayout: layout}
settings.renderCustomLayout(ctx)
return settings
}
var supportedCustomKeys = map[ui.Key]string{
ui.KeyA: "A",
ui.KeyB: "B",
ui.KeyC: "C",
ui.KeyD: "D",
ui.KeyE: "E",
ui.KeyF: "F",
ui.KeyG: "G",
ui.KeyH: "H",
ui.KeyI: "I",
ui.KeyJ: "J",
ui.KeyK: "K",
ui.KeyL: "L",
ui.KeyM: "M",
ui.KeyN: "N",
ui.KeyO: "O",
ui.KeyP: "P",
ui.KeyQ: "Q",
ui.KeyR: "R",
ui.KeyS: "S",
ui.KeyT: "T",
ui.KeyU: "U",
ui.KeyV: "V",
ui.KeyW: "W",
ui.KeyX: "X",
ui.KeyY: "Y",
ui.KeyZ: "Z",
}
func (s *settings) Handle(ctx ui.Context, e ui.Event) bool {
if s.StackPanel.Handle(ctx, e) {
return true
}
switch e := e.(type) {
case *ui.KeyDownEvent:
if s.SelectingCustom > 0 {
switch e.Key {
case ui.KeyEscape:
s.SelectingCustom = 0
return true
}
key, ok := supportedCustomKeys[e.Key]
if ok {
switch s.SelectingCustom {
case 1:
s.app.Settings.Controls.MoveUpLeft = key
case 2:
s.app.Settings.Controls.MoveDownLeft = key
case 3:
s.app.Settings.Controls.MoveDownRight = key
case 4:
s.app.Settings.Controls.MoveUpRight = key
}
s.renderCustomLayout(ctx)
s.SelectingCustom++
if s.SelectingCustom == 5 {
s.SelectingCustom = 0
s.SelectedLayout = 2
s.app.Settings.Controls.Type = controlsTypeCustom
}
}
return true
}
switch e.Key {
case ui.KeyEscape:
s.app.ShowMainMenu(ctx)
return true
case ui.KeyLeft:
s.ActiveLayout = (s.ActiveLayout + 2) % 3
case ui.KeyRight:
s.ActiveLayout = (s.ActiveLayout + 1) % 3
case ui.KeyEnter:
switch s.ActiveLayout {
case 0:
s.SelectedLayout = 0
s.app.Settings.Controls.Type = controlsTypeWASD
case 1:
s.SelectedLayout = 1
s.app.Settings.Controls.Type = controlsTypeArrows
case 2:
s.SelectingCustom = 1
}
}
}
return false
}
func (s *settings) Render(ctx ui.Context) {
bounds := s.Bounds()
center := bounds.Center()
width := bounds.Dx()
renderer := ctx.Renderer()
scale := tins2021.FindScaleRound(keyboardLayoutTextureWidth, .28*width)
font := ctx.Fonts().Font("default")
layouts := []string{
"WASD",
"ARROWS",
"CUSTOM",
}
layoutTextures := []string{"layout-wasd", "layout-arrows", "layout-custom"}
normalColor := ctx.Style().Palette.Text
highlightColor := ctx.Style().Palette.Primary
for i, layout := range layouts {
layoutLeft := (.04 + .32*float32(i)) * width
layoutCenter := layoutLeft + .14*width
textColor := normalColor
layoutColor := normalColor
if s.ActiveLayout == i {
textColor = highlightColor
}
if s.SelectedLayout == i {
layoutColor = highlightColor
}
renderer.TextAlign(font, geom.PtF32(layoutCenter, center.Y-2*font.Height()), textColor, layout, ui.AlignCenter)
renderer.DrawTexturePointOptions(ctx.Textures().ScaledByName(layoutTextures[i], scale), geom.PtF32(layoutLeft, center.Y), ui.DrawOptions{Tint: layoutColor})
}
if s.SelectingCustom > 0 {
renderer.FillRectangle(bounds, zntg.MustHexColor(`#000000CF`))
selectTexture := fmt.Sprintf("layout-select-%d", s.SelectingCustom)
layoutLeft := .36 * width
layoutCenter := layoutLeft + .14*width
renderer.TextAlign(font, geom.PtF32(layoutCenter, center.Y-2*font.Height()), normalColor, "PRESS KEY TO ASSIGN", ui.AlignCenter)
renderer.DrawTexturePoint(ctx.Textures().ScaledByName(selectTexture, scale), geom.PtF32(layoutLeft, center.Y))
}
}
func (s *settings) renderCustomLayout(ctx ui.Context) {
runeOrQuestionMark := func(s string) rune {
if len(s) == 0 {
return '?'
}
return []rune(s)[0]
}
customKeys := [4]rune{
runeOrQuestionMark(s.app.Settings.Controls.MoveUpLeft),
runeOrQuestionMark(s.app.Settings.Controls.MoveDownLeft),
runeOrQuestionMark(s.app.Settings.Controls.MoveDownRight),
runeOrQuestionMark(s.app.Settings.Controls.MoveUpRight),
}
ctx.Textures().CreateTextureGo("layout-custom", generateCustomKeys(ctx.Resources(), customKeys), true)
}
func skewedKeyboardCoordinates(x, y float64) (float64, float64) {
return x - keyboardKeySkew*y, y
}

View File

@ -8,7 +8,8 @@ import (
) )
type Settings struct { type Settings struct {
Window WindowSettings Controls ControlsSettings
Window WindowSettings
} }
func SettingsPath() (string, error) { func SettingsPath() (string, error) {
@ -34,6 +35,14 @@ func (s *Settings) Store() error {
return zntg.EncodeJSON(path, s) return zntg.EncodeJSON(path, s)
} }
type ControlsSettings struct {
Type string
MoveDownRight string
MoveDownLeft string
MoveUpLeft string
MoveUpRight string
}
type WindowSettings struct { type WindowSettings struct {
Location *geom.Point Location *geom.Point
Size *geom.Point Size *geom.Point