main.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. // Copyright 2021 The Ebiten Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package main
  15. import (
  16. "bytes"
  17. "image"
  18. _ "image/jpeg"
  19. "log"
  20. "math"
  21. "github.com/hajimehoshi/ebiten/v2"
  22. "github.com/hajimehoshi/ebiten/v2/ebitenutil"
  23. "github.com/hajimehoshi/ebiten/v2/examples/resources/images"
  24. "github.com/hajimehoshi/ebiten/v2/inpututil"
  25. )
  26. // distance between points a and b.
  27. func distance(xa, ya, xb, yb int) float64 {
  28. x := math.Abs(float64(xa - xb))
  29. y := math.Abs(float64(ya - yb))
  30. return math.Sqrt(x*x + y*y)
  31. }
  32. const (
  33. screenWidth = 640
  34. screenHeight = 480
  35. )
  36. var (
  37. gophersImage *ebiten.Image
  38. )
  39. type touch struct {
  40. originX, originY int
  41. currX, currY int
  42. duration int
  43. wasPinch, isPan bool
  44. }
  45. type pinch struct {
  46. id1, id2 ebiten.TouchID
  47. originH float64
  48. prevH float64
  49. }
  50. type pan struct {
  51. id ebiten.TouchID
  52. prevX, prevY int
  53. originX, originY int
  54. }
  55. type tap struct {
  56. X, Y int
  57. }
  58. type Game struct {
  59. x, y float64
  60. zoom float64
  61. touchIDs []ebiten.TouchID
  62. touches map[ebiten.TouchID]*touch
  63. pinch *pinch
  64. pan *pan
  65. taps []tap
  66. }
  67. func (g *Game) Update() error {
  68. // Clear the previous frame's taps.
  69. g.taps = g.taps[:0]
  70. // What touches have just ended?
  71. for id, t := range g.touches {
  72. if inpututil.IsTouchJustReleased(id) {
  73. if g.pinch != nil && (id == g.pinch.id1 || id == g.pinch.id2) {
  74. g.pinch = nil
  75. }
  76. if g.pan != nil && id == g.pan.id {
  77. g.pan = nil
  78. }
  79. // If this one has not been touched long (30 frames can be assumed
  80. // to be 500ms), or moved far, then it's a tap.
  81. diff := distance(t.originX, t.originY, t.currX, t.currY)
  82. if !t.wasPinch && !t.isPan && (t.duration <= 30 || diff < 2) {
  83. g.taps = append(g.taps, tap{
  84. X: t.currX,
  85. Y: t.currY,
  86. })
  87. }
  88. delete(g.touches, id)
  89. }
  90. }
  91. // What touches are new in this frame?
  92. g.touchIDs = inpututil.AppendJustPressedTouchIDs(g.touchIDs[:0])
  93. for _, id := range g.touchIDs {
  94. x, y := ebiten.TouchPosition(id)
  95. g.touches[id] = &touch{
  96. originX: x, originY: y,
  97. currX: x, currY: y,
  98. }
  99. }
  100. g.touchIDs = ebiten.AppendTouchIDs(g.touchIDs[:0])
  101. // Update the current position and durations of any touches that have
  102. // neither begun nor ended in this frame.
  103. for _, id := range g.touchIDs {
  104. t := g.touches[id]
  105. t.duration = inpututil.TouchPressDuration(id)
  106. t.currX, t.currY = ebiten.TouchPosition(id)
  107. }
  108. // Interpret the raw touch data that's been collected into g.touches into
  109. // gestures like two-finger pinch or single-finger pan.
  110. switch len(g.touches) {
  111. case 2:
  112. // Potentially the user is making a pinch gesture with two fingers.
  113. // If the diff between their origins is different to the diff between
  114. // their currents and if these two are not already a pinch, then this is
  115. // a new pinch!
  116. id1, id2 := g.touchIDs[0], g.touchIDs[1]
  117. t1, t2 := g.touches[id1], g.touches[id2]
  118. originDiff := distance(t1.originX, t1.originY, t2.originX, t2.originY)
  119. currDiff := distance(t1.currX, t1.currY, t2.currX, t2.currY)
  120. if g.pinch == nil && g.pan == nil && math.Abs(originDiff-currDiff) > 3 {
  121. t1.wasPinch = true
  122. t2.wasPinch = true
  123. g.pinch = &pinch{
  124. id1: id1,
  125. id2: id2,
  126. originH: originDiff,
  127. prevH: originDiff,
  128. }
  129. }
  130. case 1:
  131. // Potentially this is a new pan.
  132. id := g.touchIDs[0]
  133. t := g.touches[id]
  134. if !t.wasPinch && g.pan == nil && g.pinch == nil {
  135. diff := math.Abs(distance(t.originX, t.originY, t.currX, t.currY))
  136. if diff > 1 {
  137. t.isPan = true
  138. g.pan = &pan{
  139. id: id,
  140. originX: t.originX,
  141. originY: t.originY,
  142. prevX: t.originX,
  143. prevY: t.originY,
  144. }
  145. }
  146. }
  147. }
  148. // Copy any active pinch gesture's movement to the Game's zoom.
  149. if g.pinch != nil {
  150. x1, y1 := ebiten.TouchPosition(g.pinch.id1)
  151. x2, y2 := ebiten.TouchPosition(g.pinch.id2)
  152. curr := distance(x1, y1, x2, y2)
  153. delta := curr - g.pinch.prevH
  154. g.pinch.prevH = curr
  155. g.zoom += (delta / 100) * g.zoom
  156. if g.zoom < 0.25 {
  157. g.zoom = 0.25
  158. } else if g.zoom > 10 {
  159. g.zoom = 10
  160. }
  161. }
  162. // Copy any active pan gesture's movement to the Game's x and y pan values.
  163. if g.pan != nil {
  164. currX, currY := ebiten.TouchPosition(g.pan.id)
  165. deltaX, deltaY := currX-g.pan.prevX, currY-g.pan.prevY
  166. g.pan.prevX, g.pan.prevY = currX, currY
  167. g.x += float64(deltaX)
  168. g.y += float64(deltaY)
  169. }
  170. // If the user has tapped, then reset the Game's pan and zoom.
  171. if len(g.taps) > 0 {
  172. g.x = screenWidth / 2
  173. g.y = screenHeight / 2
  174. g.zoom = 1.0
  175. }
  176. return nil
  177. }
  178. func (g *Game) Draw(screen *ebiten.Image) {
  179. op := &ebiten.DrawImageOptions{}
  180. // Apply zoom.
  181. op.GeoM.Scale(g.zoom, g.zoom)
  182. // Apply pan.
  183. op.GeoM.Translate(g.x, g.y)
  184. // Center the image (corrected by the current zoom).
  185. w, h := gophersImage.Bounds().Dx(), gophersImage.Bounds().Dy()
  186. op.GeoM.Translate(float64(-w)/2*g.zoom, float64(-h)/2*g.zoom)
  187. screen.DrawImage(gophersImage, op)
  188. ebitenutil.DebugPrint(screen, "Use a two finger pinch to zoom, swipe with one finger to pan, or tap to reset the view")
  189. }
  190. func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
  191. return screenWidth, screenHeight
  192. }
  193. func main() {
  194. // Decode an image from the image file's byte slice.
  195. img, _, err := image.Decode(bytes.NewReader(images.Gophers_jpg))
  196. if err != nil {
  197. log.Fatal(err)
  198. }
  199. gophersImage = ebiten.NewImageFromImage(img)
  200. g := &Game{
  201. x: screenWidth / 2,
  202. y: screenHeight / 2,
  203. zoom: 1.0,
  204. touches: map[ebiten.TouchID]*touch{},
  205. }
  206. ebiten.SetWindowSize(screenWidth, screenHeight)
  207. ebiten.SetWindowTitle("Touch (Ebitengine Demo)")
  208. if err := ebiten.RunGame(g); err != nil {
  209. log.Fatal(err)
  210. }
  211. }