window.nim 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. # author: Ethosa
  2. import
  3. thirdparty/opengl,
  4. thirdparty/opengl/glu,
  5. thirdparty/sdl2,
  6. thirdparty/sdl2/image,
  7. core/color,
  8. core/input,
  9. core/exceptions,
  10. nodes/node,
  11. nodes/scene,
  12. environment,
  13. strutils,
  14. unicode,
  15. os
  16. when defined(debug):
  17. import logging
  18. discard sdl2.init(INIT_EVERYTHING)
  19. discard glSetAttribute(SDL_GL_DOUBLEBUFFER, 1)
  20. discard glSetAttribute(SDL_GL_RED_SIZE, 5)
  21. discard glSetAttribute(SDL_GL_GREEN_SIZE, 6)
  22. discard glSetAttribute(SDL_GL_BLUE_SIZE, 5)
  23. var
  24. env*: EnvironmentRef = newEnvironment()
  25. width, height: cint
  26. main_scene*: SceneRef = nil
  27. current_scene*: SceneRef = nil
  28. windowptr: WindowPtr
  29. glcontext: GlContextPtr
  30. scenes*: seq[SceneRef] = @[]
  31. paused*: bool = false
  32. running*: bool = true
  33. event = sdl2.defaultEvent
  34. # --- Callbacks --- #
  35. var
  36. mouse_on: NodeRef = nil
  37. window_created: bool = false
  38. proc display {.cdecl.} =
  39. ## Displays window.
  40. glClearColor(env.color.r, env.color.g, env.color.b, env.color.a)
  41. glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
  42. glEnable(GL_BLEND)
  43. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
  44. # Draw current scene.
  45. current_scene.drawScene(width.GLfloat, height.GLfloat, paused)
  46. press_state = -1
  47. mouse_on = nil
  48. # Update window.
  49. glFlush()
  50. windowptr.glSwapWindow()
  51. os.sleep(env.delay)
  52. proc reshape(w, h: cint) {.cdecl.} =
  53. ## This called when window resized.
  54. if w > 0 and h > 0:
  55. glViewport(0, 0, w, h)
  56. glLoadIdentity()
  57. glMatrixMode(GL_PROJECTION)
  58. glMatrixMode(GL_MODELVIEW)
  59. glOrtho(-w.GLdouble/2, w.GLdouble/2, -h.GLdouble/2, h.GLdouble/2, -w.GLdouble, w.GLdouble)
  60. gluPerspective(45.0, w/h, 1.0, 200.0)
  61. width = w
  62. height = h
  63. if current_scene != nil:
  64. current_scene.reAnchorScene(w.GLfloat, h.GLfloat, paused)
  65. template check(event, condition, conditionelif: untyped): untyped =
  66. if last_event is `event` and `condition`:
  67. press_state = 2
  68. elif `conditionelif`:
  69. press_state = 1
  70. else:
  71. press_state = 0
  72. proc mouse(button, x, y: cint, pressed: bool) {.cdecl.} =
  73. ## Handle mouse input.
  74. check(InputEventMouseButton, last_event.pressed and pressed, pressed)
  75. last_event.button_index = button
  76. last_event.x = x.float
  77. last_event.y = y.float
  78. last_event.kind = MOUSE
  79. mouse_pressed = pressed
  80. last_event.pressed = pressed
  81. current_scene.handleScene(last_event, mouse_on, paused)
  82. proc wheel(x, y: cint) {.cdecl.} =
  83. ## Handle mouse wheel input.
  84. check(InputEventMouseWheel, false, false)
  85. last_event.kind = WHEEL
  86. last_event.xrel = x.float
  87. last_event.yrel = y.float
  88. current_scene.handleScene(last_event, mouse_on, paused)
  89. proc keyboardpress(c: cint) {.cdecl.} =
  90. ## Called when press any key on keyboard.
  91. if c < 0:
  92. return
  93. let key = $c
  94. check(InputEventKeyboard, last_event.pressed, true)
  95. last_event.key = key
  96. last_event.key_int = c
  97. if key notin pressed_keys:
  98. pressed_keys.add(key)
  99. pressed_keys_ints.add(c)
  100. last_event.kind = KEYBOARD
  101. last_key_state = key_state
  102. key_state = true
  103. current_scene.handleScene(last_event, mouse_on, paused)
  104. proc textinput(c: TextInputEventPtr) {.cdecl.} =
  105. ## Called when start text input
  106. last_event.key = toRunes(join(c.text[0..<32]))[0].toUtf8()
  107. last_event.kind = TEXT
  108. last_key_state = key_state
  109. key_state = true
  110. current_scene.handleScene(last_event, mouse_on, paused)
  111. proc keyboardup(c: cint) {.cdecl.} =
  112. ## Called when any key no more pressed.
  113. if c < 0:
  114. return
  115. let key = $c
  116. check(InputEventKeyboard, false, false)
  117. last_event.key = key
  118. last_event.key_int = c
  119. last_event.kind = KEYBOARD
  120. last_key_state = key_state
  121. key_state = false
  122. var i = 0
  123. for k in pressed_keys:
  124. if k == key:
  125. pressed_keys.delete(i)
  126. pressed_keys_ints.delete(i)
  127. break
  128. inc i
  129. current_scene.handleScene(last_event, mouse_on, paused)
  130. proc motion(x, y: cint) {.cdecl.} =
  131. ## Called on any mouse motion.
  132. last_event.kind = MOTION
  133. last_event.xrel = last_event.x - x.float
  134. last_event.yrel = last_event.y - y.float
  135. last_event.x = x.float
  136. last_event.y = y.float
  137. current_scene.handleScene(last_event, mouse_on, paused)
  138. # -------------------- Public -------------------- #
  139. proc addScene*(scene: SceneRef) =
  140. ## Adds a new scenes in app.
  141. ##
  142. ## Arguments:
  143. ## - `scene` - pointer to the Scene object.
  144. if scene notin scenes:
  145. scenes.add(scene)
  146. scene.rect_size.x = width.float
  147. scene.rect_size.y = height.float
  148. proc addMainScene*(scene: SceneRef) =
  149. ## Adds a new scene in the app and set it mark it as main scene.
  150. ##
  151. ## Arguents:
  152. ## - `scene` - pointer to the Scene object.
  153. if scene notin scenes:
  154. scenes.add(scene)
  155. main_scene = scene
  156. proc centeredWindow* =
  157. var dm: DisplayMode
  158. discard getCurrentDisplayMode(0, dm)
  159. windowptr.setPosition((dm.w/2 - width/2).cint, (dm.h/2 - height/2).cint)
  160. proc changeScene*(name: string, extra: seq[tuple[k: string, v: string]] = @[]): bool {.discardable.} =
  161. ## Changes current scene.
  162. ##
  163. ## Arguments:
  164. ## - `name` - name of the added scene.
  165. result = false
  166. for scene in scenes:
  167. if scene.name == name:
  168. if current_scene != nil:
  169. current_scene.exit()
  170. current_scene.data = @[]
  171. current_scene = nil
  172. current_scene = scene
  173. current_scene.data = extra
  174. current_scene.enter()
  175. current_scene.reAnchorScene(width.GLfloat, height.GLfloat, paused)
  176. result = true
  177. break
  178. when defined(debug):
  179. debug("result of `changeScene` is ", result)
  180. proc resizeWindow*(x, y: cint) =
  181. windowptr.setSize(x, y)
  182. proc setMainScene*(name: string) =
  183. ## Set up main scene.
  184. ##
  185. ## Arguments:
  186. ## - `name` - name of the added scene.
  187. for scene in scenes:
  188. if scene.name == name:
  189. main_scene = scene
  190. break
  191. proc setTitle*(title: cstring) =
  192. ## Changes window title.
  193. if window_created:
  194. windowptr.setTitle(title)
  195. else:
  196. raise newException(WindowError, "Window not launched!")
  197. proc setIcon*(icon_path: cstring) =
  198. ## Changes window title.
  199. if window_created:
  200. windowptr.setIcon(image.load(icon_path))
  201. else:
  202. raise newException(WindowError, "Window not launched!")
  203. proc Window*(title: cstring, w: cint = 640, h: cint = 360) {.cdecl.} =
  204. ## Creates a new window pointer
  205. ##
  206. ## Arguments:
  207. ## - `title` - window title.
  208. # Set up window.
  209. once:
  210. when not defined(android) and not defined(ios):
  211. loadExtensions() # Load OpenGL extensions.
  212. discard captureMouse(True32)
  213. windowptr = createWindow(
  214. title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h,
  215. SDL_WINDOW_SHOWN or SDL_WINDOW_OPENGL or SDL_WINDOW_RESIZABLE or
  216. SDL_WINDOW_ALLOW_HIGHDPI or SDL_WINDOW_FOREIGN or SDL_WINDOW_INPUT_FOCUS or SDL_WINDOW_MOUSE_FOCUS)
  217. glcontext = windowptr.glCreateContext()
  218. # Set up OpenGL
  219. glShadeModel(GL_SMOOTH)
  220. glClear(GL_COLOR_BUFFER_BIT)
  221. glEnable(GL_COLOR_MATERIAL)
  222. glMaterialf(GL_FRONT, GL_SHININESS, 15)
  223. reshape(w, h)
  224. window_created = true
  225. proc onReshape(userdata: pointer; event: ptr Event): Bool32 {.cdecl.} =
  226. if event.kind == WindowEvent:
  227. case evWindow(event[]).event
  228. of WindowEvent_Resized, WindowEvent_SizeChanged, WindowEvent_Minimized, WindowEvent_Maximized, WindowEvent_Restored:
  229. windowptr.getSize(width, height)
  230. if env.screen_mode == SCREEN_MODE_NONE:
  231. reshape(width, height)
  232. display()
  233. else:
  234. discard
  235. False32
  236. proc windowLaunch* =
  237. if main_scene.isNil():
  238. raise newException(SceneError, "Main scene is not indicated!")
  239. changeScene(main_scene.name)
  240. when defined(debug):
  241. info("window launched")
  242. addEventWatch(onReshape, windowptr)
  243. while running:
  244. while sdl2.pollEvent(event):
  245. case event.kind
  246. of QuitEvent:
  247. running = false
  248. of KeyDown:
  249. let e = evKeyboard(event)
  250. keyboardpress(e.keysym.sym)
  251. of KeyUp:
  252. let e = evKeyboard(event)
  253. keyboardup(e.keysym.sym)
  254. of TextInput:
  255. let e = text(event)
  256. textinput(e)
  257. of MouseMotion:
  258. let e = evMouseMotion(event)
  259. motion(e.x, e.y)
  260. of MouseButtonDown:
  261. let e = evMouseButton(event)
  262. mouse(e.button.cint, e.x, e.y, true)
  263. of MouseButtonUp:
  264. let e = evMouseButton(event)
  265. mouse(e.button.cint, e.x, e.y, false)
  266. of MouseWheel:
  267. let e = evMouseWheel(event)
  268. wheel(e.x, e.y)
  269. else:
  270. discard
  271. display()
  272. current_scene.exit()
  273. sdl2.glDeleteContext(glcontext)
  274. sdl2.destroy(windowptr)
  275. sdl2.quit()