Added support for changing controls.
Changed info screen slightly.
This commit is contained in:
parent
165d1fcd26
commit
000348339d
2
TODO.md
2
TODO.md
@ -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.
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
347
cmd/tins2021/settings.go
Normal 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
|
||||||
|
}
|
11
settings.go
11
settings.go
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user