package doodle import ( "time" "git.kirsle.net/SketchyMaze/doodle/pkg/balance" "git.kirsle.net/SketchyMaze/doodle/pkg/log" "git.kirsle.net/SketchyMaze/doodle/pkg/native" "git.kirsle.net/SketchyMaze/doodle/pkg/usercfg" "git.kirsle.net/go/render" "git.kirsle.net/go/render/event" "git.kirsle.net/go/ui" ) /* Touchscreen Control functionality used in the Play Scene. */ // LoopTouchable is called as part of PlayScene.Loop while the simulation is running. // // It looks for touch events on proportional regions of the window and emulates key // input bindings to move the character, jump, etc. // // TODO: this function manipulates the event.State to set Up, Down, Left, Right and // Space keys and may need love for reconfigurable keybinds later. func (s *PlayScene) LoopTouchable(ev *event.State) { var ( middle = s.touchGetMiddleBox() cursor = render.NewPoint(ev.CursorX, ev.CursorY) ) // Don't do any of this if the mouse is over the menu bar, so // clicking on the menus doesn't make the character move or jump. if cursor.Inside(s.menubar.Rect()) || s.Supervisor.GetModal() != nil || s.Supervisor.IsPointInWindow(cursor) { return } // Detect if the player is idle. // Idle means that they are not holding any directional or otherwise input key. // Keyboard inputs and touch events from this function will set these keys. // See if it stays unset long enough to consider idle. var isGrounded = (s.Player.HasGravity() && s.Player.Grounded()) || !s.Player.HasGravity() if !ev.Up && !ev.Down && !ev.Right && !ev.Left && !ev.Space && isGrounded { if s.idleLastStart.IsZero() { s.idleLastStart = time.Now() } else if time.Since(s.idleLastStart) > balance.PlayModeIdleTimeout { if !s.playerIsIdle { log.Debug("LoopTouchable: No keys pressed in a while, idle UI start") } s.playerIsIdle = true // Fade in the hint UI by stepping up the alpha value. if s.idleHelpAlpha < balance.PlayModeAlphaMax { s.idleHelpAlpha += balance.PlayModeAlphaStep } // cap it from overflow if s.idleHelpAlpha > balance.PlayModeAlphaMax { s.idleHelpAlpha = balance.PlayModeAlphaMax } } } else { s.idleLastStart = time.Time{} s.playerIsIdle = false s.idleHelpAlpha = 0 } // Click (touch) event? if ev.Button1 { // Clicked left or right of middle = move left or right. // By default the middle box is a dead zone, but if player // is already moving laterally allow for quick precision. if ev.Left || ev.Right { if cursor.X < s.d.width/2 { ev.Left = true ev.Right = false } else if cursor.X > s.d.width/2 { ev.Right = true ev.Left = false } } else { if cursor.X < middle.X { ev.Left = true ev.Right = false } else if cursor.X > middle.X+middle.W { ev.Left = false ev.Right = true } } // Clicked above middle = jump. ev.Up = cursor.Y < middle.Y // Clicked below middle = down (antigravity) ev.Down = cursor.Y > middle.Y+middle.H // Clicked on the middle box = Use. if cursor.X >= middle.X && cursor.X <= middle.X+middle.W && cursor.Y >= middle.Y && cursor.Y <= middle.Y+middle.H { ev.Space = true // Also cancel any lateral movement. ev.Left = false ev.Right = false } s.isTouching = true } else { if s.isTouching { ev.Left = false ev.Right = false ev.Up = false ev.Down = false ev.Space = false s.isTouching = false } } } // DrawTouchable draws any UI elements if needed for the touch UI. func (s *PlayScene) DrawTouchable() { // If we are not in touch mode (user has not touched their screen at all), don't // show the hints - mouse & keyboard mode. And user opt-out setting. if !native.IsTouchScreenMode() || usercfg.Current.HideTouchHints { return } var ( middle = s.touchGetMiddleBox() background = render.RGBA(200, 200, 200, uint8(s.idleHelpAlpha)) font = balance.TouchHintsFont ) font.Color.Alpha = uint8(s.idleHelpAlpha) font.Shadow.Alpha = uint8(s.idleHelpAlpha) // If the player is idle for a while, start showing them a hint UI about // the touch screen controls. if s.playerIsIdle { // Draw the "Use" button over the middle box. useBtn := ui.NewLabel(ui.Label{ Text: "Touch here\nto 'use'\nobjects", Font: font, }) useBtn.SetBackground(background) useBtn.Resize(middle) useBtn.Compute(s.d.Engine) useBtn.Present(s.d.Engine, middle.Point()) // Move Left and Move Right hints. moveLeft := ui.NewLabel(ui.Label{ Text: "Touch here to\nmove left", Font: font, }) moveLeft.SetBackground(background) moveLeft.Compute(s.d.Engine) moveLeft.Present(s.d.Engine, render.Point{ X: (middle.X / 2) - (moveLeft.Size().W / 2), Y: (s.d.height / 2) - (moveLeft.Size().H / 2), }) // Move Left and Move Right hints. moveRight := ui.NewLabel(ui.Label{ Text: "Touch here to\nmove right", Font: font, }) moveRight.SetBackground(background) moveRight.Compute(s.d.Engine) moveRight.Present(s.d.Engine, render.Point{ X: (middle.X+middle.W+s.d.width)/2 - (moveRight.Size().W / 2), Y: (s.d.height / 2) - (moveRight.Size().H / 2), }) // Jump hints. moveUp := ui.NewLabel(ui.Label{ Text: "Touch anywhere above the middle of\nthe screen to jump up in the air", Font: font, }) moveUp.SetBackground(background) moveUp.Compute(s.d.Engine) moveUp.Present(s.d.Engine, render.Point{ X: (s.d.width / 2) - (moveUp.Size().W / 2), Y: (middle.Y / 2) - (moveUp.Size().H / 2), }) // Keybind hints. keyHints := ui.NewLabel(ui.Label{ Text: "Keyboard controls:\n" + "WASD or arrow keys for movement\n" + "Space key to 'use' objects.", Font: font, }) keyHints.SetBackground(background) keyHints.Compute(s.d.Engine) keyHints.Present(s.d.Engine, render.Point{ X: (s.d.width / 2) - (keyHints.Size().W / 2), Y: (middle.Y+middle.H+s.d.height)/2 - (keyHints.Size().H / 2), }) } } // Get the middle box of the screen and return it. // X,Y are screen positions and W,H is the box size. func (s *PlayScene) touchGetMiddleBox() render.Rect { // Carve up the screen segments. var ( // The application window dimensions. width = s.d.width height = s.d.height // The middle box. middleMinSize = 96 // minimum dimensions middle = render.Rect{ X: (width / 2) - (middleMinSize / 2), Y: (height / 2) - (middleMinSize / 2), W: middleMinSize, H: middleMinSize, } ) return middle }