Added virtual controls.
Added missing mouse pointers where applicable.
This commit is contained in:
parent
aca3a5af78
commit
19adf64c54
2
TODO.md
2
TODO.md
@ -13,4 +13,4 @@
|
|||||||
- [X] Add audio settings (music & sound volume).
|
- [X] Add audio settings (music & sound volume).
|
||||||
- [X] Hearts must be saved as well for resume.
|
- [X] Hearts must be saved as well for resume.
|
||||||
- [ ] Add demo mode.
|
- [ ] Add demo mode.
|
||||||
- [ ] Add touch controls
|
- [X] Add touch controls
|
||||||
|
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
"opslag.de/schobers/tins2021"
|
"opslag.de/schobers/tins2021"
|
||||||
"opslag.de/schobers/zntg/play"
|
"opslag.de/schobers/zntg/play"
|
||||||
"opslag.de/schobers/zntg/ui"
|
"opslag.de/schobers/zntg/ui"
|
||||||
@ -58,9 +59,20 @@ 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, a.settings, a.score, func(control ui.Control) {
|
virtual := NewVirtualControls(nil)
|
||||||
a.Content = control
|
virtual.RegisterKey("confirm", ui.KeyEnter, tins2021.MustCreateNamedTextureImage(textures, "return-key", drawReturnKey()))
|
||||||
|
virtual.RegisterKey("arrow-top-left", ui.KeyUp, tins2021.MustCreateNamedTextureImage(textures, "arrow-top-left-key", drawArrowKey(.75*geom.Pi)))
|
||||||
|
virtual.RegisterKey("arrow-top-right", ui.KeyRight, tins2021.MustCreateNamedTextureImage(textures, "arrow-top-right-key", drawArrowKey(.25*geom.Pi)))
|
||||||
|
virtual.RegisterKey("arrow-bottom-left", ui.KeyDown, tins2021.MustCreateNamedTextureImage(textures, "arrow-bottom-left-key", drawArrowKey(1.25*geom.Pi)))
|
||||||
|
virtual.RegisterKey("arrow-bottom-right", ui.KeyLeft, tins2021.MustCreateNamedTextureImage(textures, "arrow-bottom-right-key", drawArrowKey(1.75*geom.Pi)))
|
||||||
|
virtual.RegisterKey("cancel", ui.KeyEscape, tins2021.MustCreateNamedTextureImage(textures, "arrow-left-key", drawArrowKey(geom.Pi)))
|
||||||
|
|
||||||
|
virtual.Enabled = a.settings.Controls.Virtual
|
||||||
|
a.Content = virtual
|
||||||
|
a.context = newAppContext(ctx, a.settings, a.score, func(view ui.Control) {
|
||||||
|
virtual.Content = view
|
||||||
})
|
})
|
||||||
|
a.context.Virtual = virtual
|
||||||
a.context.ShowMainMenu(ctx)
|
a.context.ShowMainMenu(ctx)
|
||||||
|
|
||||||
if err := a.context.Audio.LoadSample(
|
if err := a.context.Audio.LoadSample(
|
||||||
|
@ -72,6 +72,7 @@ func (app *appContext) Play(ctx ui.Context) {
|
|||||||
|
|
||||||
app.ResetCurrentScore()
|
app.ResetCurrentScore()
|
||||||
app.show(ctx, newLevelControl(app, ctx, level))
|
app.show(ctx, newLevelControl(app, ctx, level))
|
||||||
|
app.SetVirtualKeys(true, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) PlayNext(ctx ui.Context, controller *levelController) {
|
func (app *appContext) PlayNext(ctx ui.Context, controller *levelController) {
|
||||||
@ -84,6 +85,7 @@ func (app *appContext) PlayNext(ctx ui.Context, controller *levelController) {
|
|||||||
level.Score = score
|
level.Score = score
|
||||||
level.Lives = lives
|
level.Lives = lives
|
||||||
|
|
||||||
|
app.SetVirtualKeys(true, true, true)
|
||||||
controller.Play(level)
|
controller.Play(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +123,7 @@ func (app *appContext) PlayResume(ctx ui.Context) {
|
|||||||
level.Randomize(app.Score.Current.Difficulty, numberOfStars)
|
level.Randomize(app.Score.Current.Difficulty, numberOfStars)
|
||||||
|
|
||||||
app.show(ctx, newLevelControl(app, ctx, level))
|
app.show(ctx, newLevelControl(app, ctx, level))
|
||||||
|
app.SetVirtualKeys(true, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) ResetCurrentScore() {
|
func (app *appContext) ResetCurrentScore() {
|
||||||
@ -144,22 +147,27 @@ func (app *appContext) show(ctx ui.Context, control ui.Control) {
|
|||||||
|
|
||||||
func (app *appContext) ShowCredits(ctx ui.Context) {
|
func (app *appContext) ShowCredits(ctx ui.Context) {
|
||||||
app.show(ctx, newCredits(app, ctx))
|
app.show(ctx, newCredits(app, ctx))
|
||||||
|
app.SetVirtualKeys(true, true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) ShowSettings(ctx ui.Context) {
|
func (app *appContext) ShowSettings(ctx ui.Context) {
|
||||||
app.show(ctx, newSettings(app, ctx))
|
app.show(ctx, newSettings(app, ctx))
|
||||||
|
app.SetVirtualKeys(true, true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) ShowHighscores(ctx ui.Context) {
|
func (app *appContext) ShowHighscores(ctx ui.Context) {
|
||||||
app.show(ctx, newHighscores(app, ctx))
|
app.show(ctx, newHighscores(app, ctx))
|
||||||
|
app.SetVirtualKeys(true, true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) ShowInfo(ctx ui.Context) {
|
func (app *appContext) ShowInfo(ctx ui.Context) {
|
||||||
app.show(ctx, newInfo(app, ctx))
|
app.show(ctx, newInfo(app, ctx))
|
||||||
|
app.SetVirtualKeys(true, true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) ShowMainMenu(ctx ui.Context) {
|
func (app *appContext) ShowMainMenu(ctx ui.Context) {
|
||||||
app.show(ctx, newMainMenu(app, ctx))
|
app.show(ctx, newMainMenu(app, ctx))
|
||||||
|
app.SetVirtualKeys(false, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) setMusicVolume(volume float64) {
|
func (app *appContext) setMusicVolume(volume float64) {
|
||||||
@ -180,6 +188,21 @@ func (app *appContext) setSoundVolume(volume float64) {
|
|||||||
app.Audio.SampleVolume = volume
|
app.Audio.SampleVolume = volume
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *appContext) SetVirtualKeys(cancel, confirm, controls bool) {
|
||||||
|
var topLeft, topRight, bottomRight, bottomLeft []string
|
||||||
|
if cancel {
|
||||||
|
topLeft = append(topLeft, "cancel")
|
||||||
|
}
|
||||||
|
if confirm {
|
||||||
|
topRight = append(topRight, "confirm")
|
||||||
|
}
|
||||||
|
if controls {
|
||||||
|
bottomRight = append(bottomRight, "arrow-top-right", "arrow-bottom-right")
|
||||||
|
bottomLeft = append(bottomLeft, "arrow-top-left", "arrow-bottom-left")
|
||||||
|
}
|
||||||
|
app.Virtual.SetControls(topLeft, topRight, bottomRight, bottomLeft)
|
||||||
|
}
|
||||||
|
|
||||||
func (app *appContext) switchToPlayMusic(ctx ui.Context) {
|
func (app *appContext) switchToPlayMusic(ctx ui.Context) {
|
||||||
app.playNextGameMusic(ctx)
|
app.playNextGameMusic(ctx)
|
||||||
menuMusic := app.MenuMusic
|
menuMusic := app.MenuMusic
|
||||||
|
@ -169,7 +169,8 @@ func (c *credits) openBrowser(url string) error {
|
|||||||
|
|
||||||
func (c *credits) Render(ctx ui.Context) {
|
func (c *credits) Render(ctx ui.Context) {
|
||||||
renderer := ctx.Renderer()
|
renderer := ctx.Renderer()
|
||||||
width := c.Bounds().Dx()
|
bounds := c.Bounds()
|
||||||
|
width := bounds.Dx()
|
||||||
defaultColor := ctx.Style().Palette.Text
|
defaultColor := ctx.Style().Palette.Text
|
||||||
c.enumerateContent(ctx, func(s string, i int, top, height float32, font ui.Font) {
|
c.enumerateContent(ctx, func(s string, i int, top, height float32, font ui.Font) {
|
||||||
color := defaultColor
|
color := defaultColor
|
||||||
@ -186,6 +187,6 @@ func (c *credits) Render(ctx ui.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
textWidth := font.WidthOf(s)
|
textWidth := font.WidthOf(s)
|
||||||
renderer.Text(font, geom.PtF32(.5*(width-textWidth), top), color, s)
|
renderer.Text(font, geom.PtF32(bounds.Min.X+.5*(width-textWidth), top), color, s)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,10 @@ 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 {
|
switch e.Key {
|
||||||
|
case ui.KeyEnter:
|
||||||
|
if r.Level.GameOver {
|
||||||
|
r.app.ShowHighscores(ctx)
|
||||||
|
}
|
||||||
case ui.KeyEscape:
|
case ui.KeyEscape:
|
||||||
if r.Level.StarsCollected == r.Level.Stars {
|
if r.Level.StarsCollected == r.Level.Stars {
|
||||||
r.app.SetCurrentScore(r.Level)
|
r.app.SetCurrentScore(r.Level)
|
||||||
@ -128,6 +132,8 @@ func (r *levelController) Handle(ctx ui.Context, e ui.Event) bool {
|
|||||||
|
|
||||||
checkGameOver := func() {
|
checkGameOver := func() {
|
||||||
if r.Level.GameOver {
|
if r.Level.GameOver {
|
||||||
|
r.app.SetVirtualKeys(true, true, false)
|
||||||
|
|
||||||
r.Highscore = r.updateHighscore()
|
r.Highscore = r.updateHighscore()
|
||||||
if r.Highscore {
|
if r.Highscore {
|
||||||
r.app.Audio.PlaySample("level_new_high_score.mp3")
|
r.app.Audio.PlaySample("level_new_high_score.mp3")
|
||||||
@ -137,6 +143,13 @@ func (r *levelController) Handle(ctx ui.Context, e ui.Event) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkLevelCompleted := func() {
|
||||||
|
if r.Level.StarsCollected == r.Level.Stars {
|
||||||
|
r.app.SetVirtualKeys(true, true, false)
|
||||||
|
r.app.Audio.PlaySample("level_completed.mp3")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
monsterHit := func(hit *tins2021.MonsterHit) {
|
monsterHit := func(hit *tins2021.MonsterHit) {
|
||||||
r.app.Audio.PlaySample("player_hurt.mp3")
|
r.app.Audio.PlaySample("player_hurt.mp3")
|
||||||
if hit == nil {
|
if hit == nil {
|
||||||
@ -162,9 +175,7 @@ func (r *levelController) Handle(ctx ui.Context, e ui.Event) bool {
|
|||||||
}
|
}
|
||||||
r.app.Audio.PlaySample("player_move.mp3")
|
r.app.Audio.PlaySample("player_move.mp3")
|
||||||
checkGameOver()
|
checkGameOver()
|
||||||
if r.Level.StarsCollected == r.Level.Stars {
|
checkLevelCompleted()
|
||||||
r.app.Audio.PlaySample("level_completed.mp3")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,7 +263,8 @@ func (r levelController) Render(ctx ui.Context) {
|
|||||||
centerTopSquare := geom.PtF32(.5, .5*geom.Sin32(twelfth))
|
centerTopSquare := geom.PtF32(.5, .5*geom.Sin32(twelfth))
|
||||||
delta := geom.PtF32(geom.Cos32(twelfth), .5+centerTopSquare.Y)
|
delta := geom.PtF32(geom.Cos32(twelfth), .5+centerTopSquare.Y)
|
||||||
|
|
||||||
view := r.Bounds().Size()
|
bounds := r.Bounds()
|
||||||
|
view := bounds.Size()
|
||||||
levelView := geom.PtF32(float32(r.Level.Bounds.Dx()+2)*delta.X, float32(r.Level.Bounds.Dy()+2)*delta.Y)
|
levelView := geom.PtF32(float32(r.Level.Bounds.Dx()+2)*delta.X, float32(r.Level.Bounds.Dy()+2)*delta.Y)
|
||||||
textureWidth := geom.Min32(
|
textureWidth := geom.Min32(
|
||||||
geom.Floor32(tins2021.TextureSize*view.X*.75/(levelView.X*tins2021.TextureSize)),
|
geom.Floor32(tins2021.TextureSize*view.X*.75/(levelView.X*tins2021.TextureSize)),
|
||||||
@ -262,15 +274,15 @@ func (r levelController) Render(ctx ui.Context) {
|
|||||||
|
|
||||||
delta = delta.Mul(textureWidth)
|
delta = delta.Mul(textureWidth)
|
||||||
centerTopSquare = centerTopSquare.Mul(textureWidth)
|
centerTopSquare = centerTopSquare.Mul(textureWidth)
|
||||||
scoreView := geom.RectF32(levelView.X*textureWidth, offsetY+delta.Y, view.X, view.Y-delta.Y-offsetY)
|
scoreView := geom.RectF32(levelView.X*textureWidth, offsetY+delta.Y, view.X, view.Y-delta.Y-offsetY).Add(bounds.Min)
|
||||||
|
|
||||||
delta.X = geom.Round32(delta.X)
|
delta.X = geom.Round32(delta.X)
|
||||||
delta.Y = geom.Round32(delta.Y)
|
delta.Y = geom.Round32(delta.Y)
|
||||||
toScreen := func(p geom.Point) geom.PointF32 {
|
toScreen := func(p geom.Point) geom.PointF32 {
|
||||||
if p.Y%2 == 0 {
|
if p.Y%2 == 0 {
|
||||||
return p.ToF32().Mul2D(delta.XY()).Add2D(.5*delta.X, offsetY)
|
return p.ToF32().Mul2D(delta.XY()).Add2D(.5*delta.X, offsetY).Add(bounds.Min)
|
||||||
}
|
}
|
||||||
return p.ToF32().Mul2D(delta.XY()).Add2D(0, offsetY)
|
return p.ToF32().Mul2D(delta.XY()).Add2D(0, offsetY).Add(bounds.Min)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer := ctx.Renderer()
|
renderer := ctx.Renderer()
|
||||||
@ -404,12 +416,13 @@ func (r levelController) Render(ctx ui.Context) {
|
|||||||
scoreTopLeft.Y -= scoreFont.Height()
|
scoreTopLeft.Y -= scoreFont.Height()
|
||||||
renderer.Text(scoreFont, scoreTopLeft, textColor, "Score:")
|
renderer.Text(scoreFont, scoreTopLeft, textColor, "Score:")
|
||||||
|
|
||||||
bounds := r.Bounds()
|
centerX := bounds.Min.X + .5*bounds.Dx()
|
||||||
centerX := .5 * bounds.Dx()
|
|
||||||
titleFont := ctx.Fonts().Font("title")
|
titleFont := ctx.Fonts().Font("title")
|
||||||
|
|
||||||
|
screenSize := ctx.Renderer().Size().ToF32()
|
||||||
|
screen := geom.RectF32(0, 0, screenSize.X, screenSize.Y)
|
||||||
if r.Level.GameOver {
|
if r.Level.GameOver {
|
||||||
renderer.FillRectangle(bounds, zntg.MustHexColor(`#0000007F`))
|
renderer.FillRectangle(screen, zntg.MustHexColor(`#0000007F`))
|
||||||
offsetY := .5*bounds.Dy() - titleFont.Height()
|
offsetY := .5*bounds.Dy() - titleFont.Height()
|
||||||
renderer.TextAlign(titleFont, geom.PtF32(centerX, offsetY), textColor, "GAME OVER", ui.AlignCenter)
|
renderer.TextAlign(titleFont, geom.PtF32(centerX, offsetY), textColor, "GAME OVER", ui.AlignCenter)
|
||||||
|
|
||||||
@ -422,12 +435,15 @@ func (r levelController) Render(ctx ui.Context) {
|
|||||||
offsetY += titleFont.Height() + scoreFont.Height()
|
offsetY += titleFont.Height() + scoreFont.Height()
|
||||||
renderer.TextAlign(scoreFont, geom.PtF32(centerX, offsetY), textColor, fmt.Sprintf("Final score: %d", r.Level.Score), ui.AlignCenter)
|
renderer.TextAlign(scoreFont, geom.PtF32(centerX, offsetY), textColor, fmt.Sprintf("Final score: %d", r.Level.Score), ui.AlignCenter)
|
||||||
|
|
||||||
|
offsetY += 2 * scoreFont.Height()
|
||||||
|
renderer.TextAlign(scoreFont, geom.PtF32(centerX, offsetY), textColor, "Press [enter] to show highscores.", ui.AlignCenter)
|
||||||
|
|
||||||
offsetY += 2 * scoreFont.Height()
|
offsetY += 2 * scoreFont.Height()
|
||||||
renderer.TextAlign(scoreFont, geom.PtF32(centerX, offsetY), textColor, "Press [escape] to quit.", ui.AlignCenter)
|
renderer.TextAlign(scoreFont, geom.PtF32(centerX, offsetY), textColor, "Press [escape] to quit.", ui.AlignCenter)
|
||||||
} else if r.Level.StarsCollected == r.Level.Stars {
|
} else if r.Level.StarsCollected == r.Level.Stars {
|
||||||
renderer.FillRectangle(bounds, zntg.MustHexColor(`#0000007F`))
|
renderer.FillRectangle(screen, zntg.MustHexColor(`#0000007F`))
|
||||||
offsetY := .5*bounds.Dy() - titleFont.Height()
|
offsetY := .5*bounds.Dy() - titleFont.Height()
|
||||||
renderer.TextAlign(titleFont, geom.PtF32(.5*bounds.Dx(), offsetY), textColor, "COMPLETED", ui.AlignCenter)
|
renderer.TextAlign(titleFont, geom.PtF32(centerX, offsetY), textColor, "COMPLETED", ui.AlignCenter)
|
||||||
|
|
||||||
offsetY += titleFont.Height() + scoreFont.Height()
|
offsetY += titleFont.Height() + scoreFont.Height()
|
||||||
renderer.TextAlign(scoreFont, geom.PtF32(centerX, offsetY), textColor, fmt.Sprintf("Score: %d", r.Level.Score), ui.AlignCenter)
|
renderer.TextAlign(scoreFont, geom.PtF32(centerX, offsetY), textColor, fmt.Sprintf("Score: %d", r.Level.Score), ui.AlignCenter)
|
||||||
|
@ -66,6 +66,7 @@ func (m *Menu) Handle(ctx ui.Context, e ui.Event) bool {
|
|||||||
for i, button := range m.buttons {
|
for i, button := range m.buttons {
|
||||||
if button.IsOver() {
|
if button.IsOver() {
|
||||||
m.updateActiveButton(ctx, i)
|
m.updateActiveButton(ctx, i)
|
||||||
|
ctx.Renderer().SetMouseCursor(ui.MouseCursorPointer)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,14 +36,31 @@ const volumeControlTextureHeight = 256
|
|||||||
const volumeControlTextureWidth = 2*volumeControlKnobWidth + 2*volumeControlBarWidth
|
const volumeControlTextureWidth = 2*volumeControlKnobWidth + 2*volumeControlBarWidth
|
||||||
|
|
||||||
func drawKey(ctx *draw2dimg.GraphicContext, font *truetype.Font, center geom.PointF, key rune, color color.Color) {
|
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 keyHeight_5 = .5 * keyboardKeyHeight
|
||||||
const keyWidth_5 = .5 * keyboardKeyWidth
|
|
||||||
|
|
||||||
skewed := func(x, y float64) (float64, float64) {
|
skewed := func(x, y float64) (float64, float64) {
|
||||||
x, y = skewedKeyboardCoordinates(x, y)
|
return center.X + x - keyboardKeySkew*y, center.Y + y
|
||||||
return center.X + x, center.Y + y
|
|
||||||
}
|
}
|
||||||
|
drawKeyOutline(ctx, center, geom.PtF(keyboardKeyWidth, keyboardKeyHeight), keyboardKeySkew, color)
|
||||||
|
|
||||||
|
setDraw2DFont(ctx, font)
|
||||||
|
ctx.SetFontSize(keyHeight_5)
|
||||||
|
|
||||||
|
text := fmt.Sprintf("%c", key)
|
||||||
|
textCenter := draw2DCenterString(ctx, text)
|
||||||
|
textX, textY := skewed(textCenter.X, textCenter.Y)
|
||||||
|
ctx.FillStringAt(text, textX, textY)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawKeyOutline(ctx *draw2dimg.GraphicContext, center, size geom.PointF, skew float64, color color.Color) {
|
||||||
|
const cornerRadius = keyboardKeyCornerRadius
|
||||||
|
var keyHeight_5 = .5 * size.Y
|
||||||
|
var keyWidth_5 = .5 * size.X
|
||||||
|
|
||||||
|
skewed := func(x, y float64) (float64, float64) {
|
||||||
|
return center.X + x - skew*y, center.Y + y
|
||||||
|
}
|
||||||
|
|
||||||
corner := func(x, y, start float64) {
|
corner := func(x, y, start float64) {
|
||||||
for a := start; a <= start+.25; a += .025 {
|
for a := start; a <= start+.25; a += .025 {
|
||||||
aa := a * 2 * geom.Pi
|
aa := a * 2 * geom.Pi
|
||||||
@ -65,14 +82,6 @@ func drawKey(ctx *draw2dimg.GraphicContext, font *truetype.Font, center geom.Poi
|
|||||||
corner(-keyWidth_5+cornerRadius, keyHeight_5-cornerRadius, .5)
|
corner(-keyWidth_5+cornerRadius, keyHeight_5-cornerRadius, .5)
|
||||||
ctx.Close()
|
ctx.Close()
|
||||||
ctx.Stroke()
|
ctx.Stroke()
|
||||||
|
|
||||||
setDraw2DFont(ctx, font)
|
|
||||||
ctx.SetFontSize(keyHeight_5)
|
|
||||||
|
|
||||||
text := fmt.Sprintf("%c", key)
|
|
||||||
textCenter := draw2DCenterString(ctx, text)
|
|
||||||
textX, textY := skewed(textCenter.X, textCenter.Y)
|
|
||||||
ctx.FillStringAt(text, textX, textY)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateArrowKeys(resources ui.Resources) image.Image {
|
func generateArrowKeys(resources ui.Resources) image.Image {
|
||||||
@ -211,6 +220,7 @@ type keyboardLayoutSettings struct {
|
|||||||
ui.ControlBase
|
ui.ControlBase
|
||||||
|
|
||||||
app *appContext
|
app *appContext
|
||||||
|
handler SettingHandler
|
||||||
|
|
||||||
Active bool
|
Active bool
|
||||||
ActiveLayout int
|
ActiveLayout int
|
||||||
@ -219,7 +229,7 @@ type keyboardLayoutSettings struct {
|
|||||||
SelectingCustom int
|
SelectingCustom int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newKeyboardLayoutSettings(app *appContext, ctx ui.Context) *keyboardLayoutSettings {
|
func newKeyboardLayoutSettings(app *appContext, ctx ui.Context, handler SettingHandler) *keyboardLayoutSettings {
|
||||||
ctx.Textures().CreateTextureGo("layout-wasd", generateWASDKeys(ctx.Resources()), true)
|
ctx.Textures().CreateTextureGo("layout-wasd", generateWASDKeys(ctx.Resources()), true)
|
||||||
ctx.Textures().CreateTextureGo("layout-arrows", generateArrowKeys(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-1", generateArrowKeysHighlight(ctx.Resources(), [4]bool{true, false, false, false}), true)
|
||||||
@ -235,7 +245,7 @@ func newKeyboardLayoutSettings(app *appContext, ctx ui.Context) *keyboardLayoutS
|
|||||||
layout = 2
|
layout = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
settings := &keyboardLayoutSettings{app: app, ActiveLayout: layout, SelectedLayout: layout}
|
settings := &keyboardLayoutSettings{app: app, handler: handler, ActiveLayout: layout, SelectedLayout: layout}
|
||||||
settings.renderCustomLayout(ctx)
|
settings.renderCustomLayout(ctx)
|
||||||
return settings
|
return settings
|
||||||
}
|
}
|
||||||
@ -307,6 +317,8 @@ func (s *keyboardLayoutSettings) Handle(ctx ui.Context, e ui.Event) bool {
|
|||||||
layout := s.isOverLayout(ctx, e.Pos())
|
layout := s.isOverLayout(ctx, e.Pos())
|
||||||
if layout > -1 {
|
if layout > -1 {
|
||||||
s.setActiveLayout(layout)
|
s.setActiveLayout(layout)
|
||||||
|
s.handler.Activated(ctx)
|
||||||
|
ctx.Renderer().SetMouseCursor(ui.MouseCursorPointer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.Active = s.IsOver()
|
s.Active = s.IsOver()
|
||||||
@ -324,9 +336,12 @@ func (s *keyboardLayoutSettings) Handle(ctx ui.Context, e ui.Event) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *keyboardLayoutSettings) IsActive() bool { return s.Active }
|
||||||
|
|
||||||
func (s *keyboardLayoutSettings) isOverLayout(ctx ui.Context, mouse geom.PointF32) int {
|
func (s *keyboardLayoutSettings) isOverLayout(ctx ui.Context, mouse geom.PointF32) int {
|
||||||
bounds := s.Bounds()
|
bounds := s.Bounds()
|
||||||
width := bounds.Dx()
|
width := bounds.Dx()
|
||||||
|
mouse = mouse.Sub(s.Offset())
|
||||||
|
|
||||||
scale := tins2021.FindScaleRound(keyboardLayoutTextureWidth, .28*width)
|
scale := tins2021.FindScaleRound(keyboardLayoutTextureWidth, .28*width)
|
||||||
|
|
||||||
@ -438,42 +453,87 @@ func (s *keyboardLayoutSettings) selectLayout() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *keyboardLayoutSettings) SetActive(_ ui.Context, active bool) { s.Active = active }
|
||||||
|
|
||||||
func (s *keyboardLayoutSettings) setActiveLayout(layout int) {
|
func (s *keyboardLayoutSettings) setActiveLayout(layout int) {
|
||||||
layout = (layout + 3) % 3
|
layout = (layout + 3) % 3
|
||||||
change := layout != s.ActiveLayout
|
change := layout != s.ActiveLayout
|
||||||
|
s.Active = true
|
||||||
s.ActiveLayout = (layout + 3) % 3
|
s.ActiveLayout = (layout + 3) % 3
|
||||||
if change {
|
if change {
|
||||||
s.app.MenuInteraction()
|
s.app.MenuInteraction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Setting interface {
|
||||||
|
IsActive() bool
|
||||||
|
SetActive(ui.Context, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SettingHandler interface {
|
||||||
|
Interacted()
|
||||||
|
Activated(ui.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
type settingHandler struct {
|
||||||
|
app *appContext
|
||||||
|
settings *settings
|
||||||
|
|
||||||
|
Setting int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *settingHandler) Interacted() {
|
||||||
|
h.app.MenuInteraction()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *settingHandler) Activated(ctx ui.Context) {
|
||||||
|
h.settings.setActive(ctx, h.Setting, false)
|
||||||
|
}
|
||||||
|
|
||||||
type settings struct {
|
type settings struct {
|
||||||
ui.Proxy
|
ui.Proxy
|
||||||
|
|
||||||
|
active int
|
||||||
app *appContext
|
app *appContext
|
||||||
|
overflow ui.ScrollControl
|
||||||
musicVolume *volumeControl
|
musicVolume *volumeControl
|
||||||
soundVolume *volumeControl
|
soundVolume *volumeControl
|
||||||
|
virtual *toggleControl
|
||||||
keyboard *keyboardLayoutSettings
|
keyboard *keyboardLayoutSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSettings(app *appContext, ctx ui.Context) *settings {
|
func newSettings(app *appContext, ctx ui.Context) *settings {
|
||||||
volumeControlTexture := generateTextureMapFromImage(ctx.Textures(), "volume-control", generateVolumeControlTexture)
|
volumeControlTexture := generateTextureMapFromImage(ctx.Textures(), "volume-control", generateVolumeControlTexture)
|
||||||
|
|
||||||
settings := &settings{app: app,
|
settings := &settings{app: app}
|
||||||
musicVolume: newVolumeControl(app, volumeControlTexture, "Music", app.Settings.Audio.MusicVolume, func(volume float64) { app.setMusicVolume(volume) }),
|
var s int
|
||||||
soundVolume: newVolumeControl(app, volumeControlTexture, "Sounds", app.Settings.Audio.SoundVolume, func(volume float64) { app.setSoundVolume(volume) }),
|
handler := func() *settingHandler {
|
||||||
keyboard: newKeyboardLayoutSettings(app, ctx),
|
defer func() { s++ }()
|
||||||
|
return &settingHandler{Setting: s, settings: settings, app: app}
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.Content = ui.BuildStackPanel(ui.OrientationVertical, func(p *ui.StackPanel) {
|
settings.musicVolume = newVolumeControl(handler(), volumeControlTexture, "Music", app.Settings.Audio.MusicVolume, func(volume float64) { app.setMusicVolume(volume) })
|
||||||
|
settings.soundVolume = newVolumeControl(handler(), volumeControlTexture, "Sounds", app.Settings.Audio.SoundVolume, func(volume float64) { app.setSoundVolume(volume) })
|
||||||
|
settings.virtual = newToggleControl(handler(), "VIRTUAL CONTROLS", app.Settings.Controls.Virtual, func(on bool) {
|
||||||
|
app.Settings.Controls.Virtual = on
|
||||||
|
app.Virtual.Enabled = on
|
||||||
|
})
|
||||||
|
settings.keyboard = newKeyboardLayoutSettings(app, ctx, handler())
|
||||||
|
|
||||||
|
settings.Content = ui.StretchWidth(ui.BuildStackPanel(ui.OrientationVertical, func(p *ui.StackPanel) {
|
||||||
p.AddChild(Center(label("SETTINGS", "title")))
|
p.AddChild(Center(label("SETTINGS", "title")))
|
||||||
p.AddChild(label("", "score"))
|
p.AddChild(label("", "score"))
|
||||||
|
settings.overflow = ui.Overflow(ui.BuildStackPanel(ui.OrientationVertical, func(p *ui.StackPanel) {
|
||||||
p.AddChild(Center(settings.musicVolume))
|
p.AddChild(Center(settings.musicVolume))
|
||||||
p.AddChild(label("", "score"))
|
p.AddChild(label("", "score"))
|
||||||
p.AddChild(Center(settings.soundVolume))
|
p.AddChild(Center(settings.soundVolume))
|
||||||
p.AddChild(label("", "score"))
|
p.AddChild(label("", "score"))
|
||||||
|
p.AddChild(Center(settings.virtual))
|
||||||
|
p.AddChild(label("", "score"))
|
||||||
p.AddChild(settings.keyboard)
|
p.AddChild(settings.keyboard)
|
||||||
})
|
}))
|
||||||
|
p.AddChild(settings.overflow)
|
||||||
|
}))
|
||||||
|
|
||||||
settings.musicVolume.Active = true
|
settings.musicVolume.Active = true
|
||||||
|
|
||||||
@ -486,8 +546,10 @@ func (s *settings) currentActive() int {
|
|||||||
return 0
|
return 0
|
||||||
case s.soundVolume.Active:
|
case s.soundVolume.Active:
|
||||||
return 1
|
return 1
|
||||||
case s.keyboard.Active:
|
case s.virtual.Active:
|
||||||
return 2
|
return 2
|
||||||
|
case s.keyboard.Active:
|
||||||
|
return 3
|
||||||
default:
|
default:
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -502,9 +564,9 @@ func (s *settings) Handle(ctx ui.Context, e ui.Event) bool {
|
|||||||
case *ui.KeyDownEvent:
|
case *ui.KeyDownEvent:
|
||||||
switch e.Key {
|
switch e.Key {
|
||||||
case ui.KeyUp:
|
case ui.KeyUp:
|
||||||
s.setActive(s.currentActive() - 1)
|
s.setActive(ctx, s.currentActive()-1, true)
|
||||||
case ui.KeyDown:
|
case ui.KeyDown:
|
||||||
s.setActive(s.currentActive() + 1)
|
s.setActive(ctx, s.currentActive()+1, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,11 +578,12 @@ func (s *settings) Render(ctx ui.Context) {
|
|||||||
s.keyboard.PostRender(ctx)
|
s.keyboard.PostRender(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *settings) setActive(active int) {
|
func (s *settings) setActive(ctx ui.Context, active int, key bool) {
|
||||||
controls := [3]*bool{
|
controls := []Setting{
|
||||||
&s.musicVolume.Active,
|
s.musicVolume,
|
||||||
&s.soundVolume.Active,
|
s.soundVolume,
|
||||||
&s.keyboard.Active,
|
s.virtual,
|
||||||
|
s.keyboard,
|
||||||
}
|
}
|
||||||
for active < 0 {
|
for active < 0 {
|
||||||
active += len(controls)
|
active += len(controls)
|
||||||
@ -528,17 +591,20 @@ func (s *settings) setActive(active int) {
|
|||||||
if active > 0 {
|
if active > 0 {
|
||||||
active = active % len(controls)
|
active = active % len(controls)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.active != active {
|
||||||
|
s.active = active
|
||||||
|
if key {
|
||||||
|
controls[active].(ui.Control).ScrollIntoView(ctx, nil)
|
||||||
|
}
|
||||||
s.app.MenuInteraction()
|
s.app.MenuInteraction()
|
||||||
|
}
|
||||||
|
|
||||||
for i := range controls {
|
for i := range controls {
|
||||||
*controls[i] = i == active
|
controls[i].SetActive(ctx, i == active)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func skewedKeyboardCoordinates(x, y float64) (float64, float64) {
|
|
||||||
return x - keyboardKeySkew*y, y
|
|
||||||
}
|
|
||||||
|
|
||||||
var supportedCustomKeys = map[ui.Key]string{
|
var supportedCustomKeys = map[ui.Key]string{
|
||||||
ui.KeyA: "A",
|
ui.KeyA: "A",
|
||||||
ui.KeyB: "B",
|
ui.KeyB: "B",
|
||||||
@ -568,10 +634,85 @@ var supportedCustomKeys = map[ui.Key]string{
|
|||||||
ui.KeyZ: "Z",
|
ui.KeyZ: "Z",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type toggleControl struct {
|
||||||
|
ui.StackPanel
|
||||||
|
|
||||||
|
handler SettingHandler
|
||||||
|
caption *ui.Label
|
||||||
|
on *ui.Label
|
||||||
|
changed func(bool)
|
||||||
|
|
||||||
|
Active bool
|
||||||
|
On bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newToggleControl(handler SettingHandler, name string, on bool, changed func(bool)) *toggleControl {
|
||||||
|
center := func(l *ui.Label) { l.TextAlignment = ui.AlignCenter }
|
||||||
|
toggle := &toggleControl{handler: handler, On: on, caption: ui.BuildLabel(name, center), on: ui.BuildLabel("", center), changed: changed}
|
||||||
|
toggle.AddChild(toggle.caption)
|
||||||
|
toggle.AddChild(toggle.on)
|
||||||
|
toggle.updateOn()
|
||||||
|
return toggle
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *toggleControl) toggle() {
|
||||||
|
c.On = !c.On
|
||||||
|
c.updateOn()
|
||||||
|
c.handler.Interacted()
|
||||||
|
changed := c.changed
|
||||||
|
if changed != nil {
|
||||||
|
changed(c.On)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *toggleControl) Handle(ctx ui.Context, e ui.Event) bool {
|
||||||
|
if c.ControlBase.Handle(ctx, e) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch e := e.(type) {
|
||||||
|
case *ui.MouseMoveEvent:
|
||||||
|
if !c.Active && c.IsOver() {
|
||||||
|
c.Active = true
|
||||||
|
c.handler.Activated(ctx)
|
||||||
|
}
|
||||||
|
if c.IsOver() {
|
||||||
|
ctx.Renderer().SetMouseCursor(ui.MouseCursorPointer)
|
||||||
|
}
|
||||||
|
case *ui.MouseButtonDownEvent:
|
||||||
|
if e.Button == ui.MouseButtonLeft && c.Active && c.IsOver() {
|
||||||
|
c.toggle()
|
||||||
|
}
|
||||||
|
case *ui.KeyDownEvent:
|
||||||
|
if c.Active && e.Key == ui.KeyEnter {
|
||||||
|
c.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *toggleControl) IsActive() bool { return c.Active }
|
||||||
|
|
||||||
|
func (c *toggleControl) SetActive(ctx ui.Context, active bool) {
|
||||||
|
c.Active = active
|
||||||
|
if active {
|
||||||
|
c.caption.Font.Color = ctx.Style().Palette.Primary
|
||||||
|
} else {
|
||||||
|
c.caption.Font.Color = ctx.Style().Palette.Text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *toggleControl) updateOn() {
|
||||||
|
if c.On {
|
||||||
|
c.on.Text = "ON"
|
||||||
|
} else {
|
||||||
|
c.on.Text = "OFF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type volumeControl struct {
|
type volumeControl struct {
|
||||||
ui.ControlBase
|
ui.ControlBase
|
||||||
|
|
||||||
app *appContext
|
handler SettingHandler
|
||||||
texture *TextureMap
|
texture *TextureMap
|
||||||
|
|
||||||
Active bool
|
Active bool
|
||||||
@ -584,8 +725,8 @@ type volumeControl struct {
|
|||||||
VolumeChanged func(float64)
|
VolumeChanged func(float64)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newVolumeControl(app *appContext, texture *TextureMap, name string, volume float64, changed func(float64)) *volumeControl {
|
func newVolumeControl(handler SettingHandler, texture *TextureMap, name string, volume float64, changed func(float64)) *volumeControl {
|
||||||
control := &volumeControl{app: app, texture: texture, overBar: -1, Name: name, Volume: volume, VolumeChanged: changed}
|
control := &volumeControl{handler: handler, texture: texture, overBar: -1, Name: name, Volume: volume, VolumeChanged: changed}
|
||||||
return control
|
return control
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -593,16 +734,6 @@ func (c *volumeControl) changeVolume(delta float64) {
|
|||||||
c.setVolume(c.Volume + delta)
|
c.setVolume(c.Volume + delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *volumeControl) setVolume(volume float64) {
|
|
||||||
volume = geom.Min(maxVolume, geom.Max(minVolume, volume))
|
|
||||||
if c.Volume == volume {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Volume = volume
|
|
||||||
c.VolumeChanged(c.Volume)
|
|
||||||
c.app.MenuInteraction()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *volumeControl) DesiredSize(ctx ui.Context, size geom.PointF32) geom.PointF32 {
|
func (c *volumeControl) DesiredSize(ctx ui.Context, size geom.PointF32) geom.PointF32 {
|
||||||
font := ctx.Fonts().Font("default")
|
font := ctx.Fonts().Font("default")
|
||||||
return geom.PtF32(geom.NaN32(), 2.5*font.Height())
|
return geom.PtF32(geom.NaN32(), 2.5*font.Height())
|
||||||
@ -633,27 +764,34 @@ func (c *volumeControl) Handle(ctx ui.Context, e ui.Event) bool {
|
|||||||
c.overRightKnob = false
|
c.overRightKnob = false
|
||||||
case 0:
|
case 0:
|
||||||
if !c.overLeftKnob {
|
if !c.overLeftKnob {
|
||||||
c.app.MenuInteraction()
|
c.handler.Interacted()
|
||||||
}
|
}
|
||||||
c.overLeftKnob = true
|
c.overLeftKnob = true
|
||||||
c.overBar = -1
|
c.overBar = -1
|
||||||
c.overRightKnob = false
|
c.overRightKnob = false
|
||||||
case 11:
|
case 11:
|
||||||
if !c.overRightKnob {
|
if !c.overRightKnob {
|
||||||
c.app.MenuInteraction()
|
c.handler.Interacted()
|
||||||
}
|
}
|
||||||
c.overLeftKnob = false
|
c.overLeftKnob = false
|
||||||
c.overBar = -1
|
c.overBar = -1
|
||||||
c.overRightKnob = true
|
c.overRightKnob = true
|
||||||
default:
|
default:
|
||||||
if over-1 != c.overBar {
|
if over-1 != c.overBar {
|
||||||
c.app.MenuInteraction()
|
c.handler.Interacted()
|
||||||
}
|
}
|
||||||
c.overLeftKnob = false
|
c.overLeftKnob = false
|
||||||
c.overBar = over - 1
|
c.overBar = over - 1
|
||||||
c.overRightKnob = false
|
c.overRightKnob = false
|
||||||
}
|
}
|
||||||
|
if over > -1 {
|
||||||
|
ctx.Renderer().SetMouseCursor(ui.MouseCursorPointer)
|
||||||
|
}
|
||||||
|
active := c.Active
|
||||||
c.Active = c.IsOver()
|
c.Active = c.IsOver()
|
||||||
|
if !active && c.Active {
|
||||||
|
c.handler.Activated(ctx)
|
||||||
|
}
|
||||||
case *ui.MouseButtonDownEvent:
|
case *ui.MouseButtonDownEvent:
|
||||||
if e.Button == ui.MouseButtonLeft {
|
if e.Button == ui.MouseButtonLeft {
|
||||||
over := c.isOver(ctx, e.Pos())
|
over := c.isOver(ctx, e.Pos())
|
||||||
@ -671,8 +809,11 @@ func (c *volumeControl) Handle(ctx ui.Context, e ui.Event) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *volumeControl) IsActive() bool { return c.Active }
|
||||||
|
|
||||||
func (c *volumeControl) isOver(ctx ui.Context, p geom.PointF32) int {
|
func (c *volumeControl) isOver(ctx ui.Context, p geom.PointF32) int {
|
||||||
bounds := c.Bounds()
|
bounds := c.Bounds()
|
||||||
|
p = p.Sub(c.Offset())
|
||||||
font := ctx.Fonts().Font("default")
|
font := ctx.Fonts().Font("default")
|
||||||
|
|
||||||
top := bounds.Min.Y + 1.5*font.Height()
|
top := bounds.Min.Y + 1.5*font.Height()
|
||||||
@ -744,3 +885,15 @@ func (c *volumeControl) Render(ctx ui.Context) {
|
|||||||
}
|
}
|
||||||
ctx.Renderer().DrawTexturePointOptions(rightKnob, geom.PtF32(left, top), ui.DrawOptions{Source: &rightKnobRegion, Tint: tint(c.overRightKnob)})
|
ctx.Renderer().DrawTexturePointOptions(rightKnob, geom.PtF32(left, top), ui.DrawOptions{Source: &rightKnobRegion, Tint: tint(c.overRightKnob)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *volumeControl) SetActive(_ ui.Context, active bool) { c.Active = active }
|
||||||
|
|
||||||
|
func (c *volumeControl) setVolume(volume float64) {
|
||||||
|
volume = geom.Min(maxVolume, geom.Max(minVolume, volume))
|
||||||
|
if c.Volume == volume {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Volume = volume
|
||||||
|
c.VolumeChanged(c.Volume)
|
||||||
|
c.handler.Interacted()
|
||||||
|
}
|
||||||
|
237
cmd/tins2021/virtualcontrols.go
Normal file
237
cmd/tins2021/virtualcontrols.go
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
|
"github.com/llgcode/draw2d/draw2dimg"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/geom/ints"
|
||||||
|
"opslag.de/schobers/tins2021"
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func drawArrowKey(a float64) image.Image {
|
||||||
|
const keyHeight_5 = .5 * tins2021.TextureSize
|
||||||
|
const keyWidth_5 = .5 * tins2021.TextureSize
|
||||||
|
|
||||||
|
im := image.NewRGBA(image.Rect(0, 0, tins2021.TextureSize, tins2021.TextureSize))
|
||||||
|
ctx := draw2dimg.NewGraphicContext(im)
|
||||||
|
center := geom.PtF(keyWidth_5, keyHeight_5)
|
||||||
|
drawKeyOutline(ctx, center, geom.PtF(.95*tins2021.TextureSize, .76*tins2021.TextureSize), 0, color.White)
|
||||||
|
point := center.Add(tins2021.Polar(a, .25*tins2021.TextureSize))
|
||||||
|
back := center.Add(tins2021.Polar(a+geom.Pi, .25*tins2021.TextureSize))
|
||||||
|
|
||||||
|
ctx.SetLineWidth(3)
|
||||||
|
ctx.SetStrokeColor(color.White)
|
||||||
|
ctx.SetLineCap(draw2d.RoundCap)
|
||||||
|
|
||||||
|
ctx.MoveTo(back.XY())
|
||||||
|
ctx.LineTo(point.XY())
|
||||||
|
ctx.LineTo(point.Add(tins2021.Polar(a+.75*geom.Pi, .25*tins2021.TextureSize)).XY())
|
||||||
|
ctx.Stroke()
|
||||||
|
ctx.MoveTo(point.XY())
|
||||||
|
ctx.LineTo(point.Add(tins2021.Polar(a-.75*geom.Pi, .25*tins2021.TextureSize)).XY())
|
||||||
|
ctx.Stroke()
|
||||||
|
|
||||||
|
return im
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawReturnKey() image.Image {
|
||||||
|
const keyHeight_5 = .5 * tins2021.TextureSize
|
||||||
|
const keyWidth_5 = .5 * tins2021.TextureSize
|
||||||
|
|
||||||
|
im := image.NewRGBA(image.Rect(0, 0, tins2021.TextureSize, tins2021.TextureSize))
|
||||||
|
ctx := draw2dimg.NewGraphicContext(im)
|
||||||
|
drawKeyOutline(ctx, geom.PtF(keyWidth_5, keyHeight_5), geom.PtF(.95*tins2021.TextureSize, .76*tins2021.TextureSize), 0, color.White)
|
||||||
|
|
||||||
|
unity := func(f float64) float64 { return f * tins2021.TextureSize }
|
||||||
|
|
||||||
|
ctx.SetLineWidth(3)
|
||||||
|
ctx.SetStrokeColor(color.White)
|
||||||
|
ctx.SetLineCap(draw2d.RoundCap)
|
||||||
|
|
||||||
|
ctx.MoveTo(unity(.75), unity(.3))
|
||||||
|
ctx.LineTo(unity(.75), unity(.55))
|
||||||
|
ctx.LineTo(unity(.25), unity(.55))
|
||||||
|
ctx.LineTo(unity(.4), unity(.7))
|
||||||
|
ctx.Stroke()
|
||||||
|
ctx.MoveTo(unity(.25), unity(.55))
|
||||||
|
ctx.LineTo(unity(.4), unity(.4))
|
||||||
|
ctx.Stroke()
|
||||||
|
|
||||||
|
return im
|
||||||
|
}
|
||||||
|
|
||||||
|
type VirtualControls struct {
|
||||||
|
ui.Proxy
|
||||||
|
|
||||||
|
bounds geom.RectangleF32
|
||||||
|
|
||||||
|
Enabled bool
|
||||||
|
Keys map[string]VirtualKey
|
||||||
|
TopLeftControls []string
|
||||||
|
TopRightControls []string
|
||||||
|
BottomRightControls []string
|
||||||
|
BottomLeftControls []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVirtualControls(content ui.Control) *VirtualControls {
|
||||||
|
return &VirtualControls{
|
||||||
|
Proxy: ui.Proxy{Content: content},
|
||||||
|
Keys: map[string]VirtualKey{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VirtualControls) allKeys() map[string]geom.RectangleF32 {
|
||||||
|
bounds := c.bounds
|
||||||
|
size, _, margin := c.lengths()
|
||||||
|
|
||||||
|
keySize := geom.PtF32(size, size)
|
||||||
|
keys := map[string]geom.RectangleF32{}
|
||||||
|
left := float32(margin)
|
||||||
|
top := bounds.Min.Y
|
||||||
|
for _, name := range c.TopLeftControls {
|
||||||
|
keys[name] = geom.PtF32(left, top).RectRel(keySize)
|
||||||
|
top += size + margin
|
||||||
|
}
|
||||||
|
|
||||||
|
left = bounds.Max.X - size - margin
|
||||||
|
top = bounds.Min.Y
|
||||||
|
for _, name := range c.TopRightControls {
|
||||||
|
keys[name] = geom.PtF32(left, top).RectRel(keySize)
|
||||||
|
top += size + margin
|
||||||
|
}
|
||||||
|
|
||||||
|
left = float32(margin)
|
||||||
|
top = bounds.Max.Y - size
|
||||||
|
for _, name := range c.BottomLeftControls {
|
||||||
|
keys[name] = geom.PtF32(left, top).RectRel(keySize)
|
||||||
|
top -= size + margin
|
||||||
|
}
|
||||||
|
|
||||||
|
left = bounds.Max.X - size - margin
|
||||||
|
top = bounds.Max.Y - size
|
||||||
|
for _, name := range c.BottomRightControls {
|
||||||
|
keys[name] = geom.PtF32(left, top).RectRel(keySize)
|
||||||
|
top -= size + margin
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VirtualControls) Arrange(ctx ui.Context, bounds geom.RectangleF32, offset geom.PointF32, parent ui.Control) {
|
||||||
|
c.bounds = bounds
|
||||||
|
if c.Enabled {
|
||||||
|
virtualControlSize, _ := c.desiredVirtualControlSize(bounds.Size())
|
||||||
|
bounds.Min.X += virtualControlSize
|
||||||
|
bounds.Max.X -= virtualControlSize
|
||||||
|
if bounds.Max.X < bounds.Min.X {
|
||||||
|
center := .5 * (bounds.Min.X + bounds.Max.X)
|
||||||
|
bounds.Min.X = center
|
||||||
|
bounds.Max.X = center
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Content.Arrange(ctx, bounds, offset, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VirtualControls) desiredVirtualControlSize(size geom.PointF32) (float32, int) {
|
||||||
|
if size.X > 2*size.Y {
|
||||||
|
return size.Y / 5, 2
|
||||||
|
}
|
||||||
|
return size.Y / 7, 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VirtualControls) DesiredSize(ctx ui.Context, size geom.PointF32) geom.PointF32 {
|
||||||
|
desired := c.Content.DesiredSize(ctx, size)
|
||||||
|
if geom.IsNaN32(desired.X) {
|
||||||
|
return desired
|
||||||
|
}
|
||||||
|
if c.Enabled {
|
||||||
|
virtualControlSize, rows := c.desiredVirtualControlSize(size)
|
||||||
|
columns := ints.Max(len(c.TopLeftControls)+rows-1/rows, len(c.BottomLeftControls)+rows-1/rows) + ints.Max(len(c.TopRightControls)+rows-1/rows, len(c.BottomRightControls)+rows-1/rows)
|
||||||
|
fmt.Println(columns)
|
||||||
|
desired.X = geom.Min32(desired.X+float32(columns)*virtualControlSize, size.X)
|
||||||
|
}
|
||||||
|
return desired
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VirtualControls) Handle(ctx ui.Context, e ui.Event) bool {
|
||||||
|
if c.Enabled {
|
||||||
|
switch e := e.(type) {
|
||||||
|
case *ui.MouseMoveEvent:
|
||||||
|
pos := e.Pos()
|
||||||
|
for _, bounds := range c.allKeys() {
|
||||||
|
if pos.In(bounds) {
|
||||||
|
ctx.Renderer().SetMouseCursor(ui.MouseCursorPointer)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *ui.MouseButtonDownEvent:
|
||||||
|
if e.Button == ui.MouseButtonLeft {
|
||||||
|
pos := e.Pos()
|
||||||
|
for name, bounds := range c.allKeys() {
|
||||||
|
if pos.In(bounds) {
|
||||||
|
key := c.Keys[name]
|
||||||
|
c.Content.Handle(ctx, &ui.KeyDownEvent{
|
||||||
|
EventBase: ui.EventBase{StampInSeconds: e.StampInSeconds},
|
||||||
|
Key: key.Key,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.Content.Handle(ctx, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VirtualControls) lengths() (size, scale, margin float32) {
|
||||||
|
bounds := c.bounds
|
||||||
|
margin = 8
|
||||||
|
size, _ = c.desiredVirtualControlSize(bounds.Size())
|
||||||
|
size -= margin
|
||||||
|
scale = tins2021.FindScaleRound(tins2021.TextureSize, size)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VirtualControls) RegisterKey(id string, key ui.Key, symbol tins2021.NamedTexture) {
|
||||||
|
c.Keys[id] = VirtualKey{Key: key, Symbol: symbol}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VirtualControls) Render(ctx ui.Context) {
|
||||||
|
c.Content.Render(ctx)
|
||||||
|
|
||||||
|
if c.Enabled {
|
||||||
|
_, scale, _ := c.lengths()
|
||||||
|
|
||||||
|
mouse := ctx.MousePosition()
|
||||||
|
normalColor := ctx.Style().Palette.Text
|
||||||
|
overColor := ctx.Style().Palette.Primary
|
||||||
|
|
||||||
|
renderer := ctx.Renderer()
|
||||||
|
|
||||||
|
for name, keyBounds := range c.allKeys() {
|
||||||
|
key := c.Keys[name]
|
||||||
|
texture := key.Symbol.Scaled(scale)
|
||||||
|
color := normalColor
|
||||||
|
if mouse.In(keyBounds) {
|
||||||
|
color = overColor
|
||||||
|
}
|
||||||
|
renderer.DrawTexturePointOptions(texture, keyBounds.Min, ui.DrawOptions{Tint: color})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VirtualControls) SetControls(topLeft, topRight, bottomRight, bottomLeft []string) {
|
||||||
|
c.TopLeftControls = topLeft
|
||||||
|
c.TopRightControls = topRight
|
||||||
|
c.BottomRightControls = bottomRight
|
||||||
|
c.BottomLeftControls = bottomLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
type VirtualKey struct {
|
||||||
|
Symbol tins2021.NamedTexture
|
||||||
|
Key ui.Key
|
||||||
|
}
|
@ -19,6 +19,7 @@ type ControlsSettings struct {
|
|||||||
MoveDownLeft string
|
MoveDownLeft string
|
||||||
MoveUpLeft string
|
MoveUpLeft string
|
||||||
MoveUpRight string
|
MoveUpRight string
|
||||||
|
Virtual bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user