diff --git a/TODO.md b/TODO.md index 7fc488b..b23cef4 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,7 @@ - [X] Increase difficulty. - [ ] Add music & sounds. - [ ] 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] Add monster animations (~~jumping on tile &~~ towards new tile). - [ ] Scale icons (heart & star on right side) when playing. diff --git a/animatedtexture.go b/animatedtexture.go index 71fb708..5617474 100644 --- a/animatedtexture.go +++ b/animatedtexture.go @@ -29,7 +29,7 @@ func NewAnimatedTexture(texture NamedTexture, n int) AnimatedTexture { func FitAnimatedTexture(texture NamedTexture, scale float32, n int) AnimatedTexture { 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) } @@ -44,3 +44,11 @@ func (t AnimatedTexture) Frames() int { return len(t.frames) } func (t AnimatedTexture) Scale(scale float32) AnimatedTexture { 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 +} diff --git a/cmd/tins2021/app.go b/cmd/tins2021/app.go index 5c83568..682b3ae 100644 --- a/cmd/tins2021/app.go +++ b/cmd/tins2021/app.go @@ -55,7 +55,7 @@ func (a *app) Init(ctx ui.Context) error { 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.context.ShowMainMenu(ctx) diff --git a/cmd/tins2021/appcontext.go b/cmd/tins2021/appcontext.go index 1790338..2f61b3c 100644 --- a/cmd/tins2021/appcontext.go +++ b/cmd/tins2021/appcontext.go @@ -8,7 +8,8 @@ import ( type appContext struct { setView func(ui.Control) - Debug bool + Settings *tins2021.Settings + Debug bool StarTexture tins2021.AnimatedTexture HeartTexture tins2021.AnimatedTexture @@ -16,10 +17,11 @@ type appContext struct { 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{} app := &appContext{ setView: setView, + Settings: settings, StarTexture: newAnimatedTexture(ctx, "star", defaultAnimationFrames, textures.Star), HeartTexture: newAnimatedTexture(ctx, "heart", defaultAnimationFrames, textures.Heart), MonsterTextures: map[tins2021.MonsterType]tins2021.AnimatedTexture{ @@ -62,6 +64,10 @@ func (app *appContext) ShowCredits(ctx ui.Context) { app.setView(newCredits(app, ctx)) } +func (app *appContext) ShowSettings(ctx ui.Context) { + app.setView(newSettings(app, ctx)) +} + func (app *appContext) ShowInfo(ctx ui.Context) { app.setView(newInfo(app, ctx)) } diff --git a/cmd/tins2021/info.go b/cmd/tins2021/info.go index 8ac7139..36b87bb 100644 --- a/cmd/tins2021/info.go +++ b/cmd/tins2021/info.go @@ -6,14 +6,19 @@ import ( "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 { ui.StackPanel 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 { ui.ControlBase @@ -22,44 +27,42 @@ type infoLegend struct { } 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) { bounds := l.Bounds() - separator := .3 * bounds.Dx() textureHeight := float32(l.Icon.Height()) 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") 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 { legend := ui.BuildStackPanel(ui.OrientationVertical, func(p *ui.StackPanel) { p.AddChild(&infoLegend{ - Icon: ctx.Textures().ScaledByName("star", infoLegenIconSize), + Icon: ctx.Textures().ScaledByName("star", infoLegendIconSize), Description: "Collect them to complete the level.", }) p.AddChild(&infoLegend{ - Icon: ctx.Textures().ScaledByName("heart", infoLegenIconSize), + Icon: ctx.Textures().ScaledByName("heart", infoLegendIconSize), Description: "Gives (back) a life.", }) 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.", }) p.AddChild(&infoLegend{ - Icon: ctx.Textures().ScaledByName("random-walking-monster", infoLegenIconSize), + Icon: ctx.Textures().ScaledByName("random-walking-monster", infoLegendIconSize), Description: "Monster that walks randomly.", }) p.AddChild(&infoLegend{ - Icon: ctx.Textures().ScaledByName("chasing-monster", infoLegenIconSize), + Icon: ctx.Textures().ScaledByName("chasing-monster", infoLegendIconSize), Description: "Monster that walks towards you.", }) }) @@ -70,7 +73,7 @@ func newInfo(app *appContext, ctx ui.Context) ui.Control { label("", "score"), // spacing paragraphOpts(infoText, "score", labelOptions{TextAlignment: ui.AlignCenter}), label("", "score"), // spacing - legend, + Center(legend), }, }, Orientation: ui.OrientationVertical, diff --git a/cmd/tins2021/levelcontroller.go b/cmd/tins2021/levelcontroller.go index d6e1762..1e3b1a4 100644 --- a/cmd/tins2021/levelcontroller.go +++ b/cmd/tins2021/levelcontroller.go @@ -28,6 +28,8 @@ type levelController struct { MovingMonsters *tins2021.Animations SmallFont *tins2021.BitmapFont + + Controls map[ui.Key]tins2021.Direction } 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) } 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) @@ -77,15 +110,9 @@ func (r *levelController) Handle(ctx ui.Context, e ui.Event) bool { switch e := e.(type) { case *ui.KeyDownEvent: - switch e.Key { - case ui.KeyW: - r.Level.MovePlayer(tins2021.DirectionUpLeft) - case ui.KeyD: - r.Level.MovePlayer(tins2021.DirectionUpRight) - case ui.KeyS: - r.Level.MovePlayer(tins2021.DirectionDownRight) - case ui.KeyA: - r.Level.MovePlayer(tins2021.DirectionDownLeft) + dir, ok := r.Controls[e.Key] + if ok { + r.Level.MovePlayer(dir) } } diff --git a/cmd/tins2021/mainmenu.go b/cmd/tins2021/mainmenu.go index 1cbc688..0048a24 100644 --- a/cmd/tins2021/mainmenu.go +++ b/cmd/tins2021/mainmenu.go @@ -37,6 +37,7 @@ func newMainMenu(app *appContext, ctx ui.Context) ui.Control { menu.Add("Play", func(ctx ui.Context) { 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("Quit", func(ctx ui.Context) { ctx.Quit() }) diff --git a/cmd/tins2021/settings.go b/cmd/tins2021/settings.go new file mode 100644 index 0000000..7af9ea8 --- /dev/null +++ b/cmd/tins2021/settings.go @@ -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 +} diff --git a/settings.go b/settings.go index c0c6faf..9476566 100644 --- a/settings.go +++ b/settings.go @@ -8,7 +8,8 @@ import ( ) type Settings struct { - Window WindowSettings + Controls ControlsSettings + Window WindowSettings } func SettingsPath() (string, error) { @@ -34,6 +35,14 @@ func (s *Settings) Store() error { return zntg.EncodeJSON(path, s) } +type ControlsSettings struct { + Type string + MoveDownRight string + MoveDownLeft string + MoveUpLeft string + MoveUpRight string +} + type WindowSettings struct { Location *geom.Point Size *geom.Point